Files
cozsweet-frontend-nextjs/src/stores/user/user-context.tsx
T

66 lines
1.8 KiB
TypeScript

"use client";
/**
* UserContext:基于 XState actor context 和 selector 的 React Provider。
*/
import type { Dispatch, ReactNode } from "react";
import { createActorContext, shallowEqual } from "@xstate/react";
import type { SnapshotFrom } from "xstate";
import { userMachine } from "./user-machine";
import type { UserState as MachineContext, UserEvent } from "./user-machine";
/**
* 对外暴露的 State 形状(保持与原 UserState 兼容)
*/
interface UserState {
currentUser: MachineContext["currentUser"];
isLoading: boolean;
avatarUrl: string | null;
isVip: boolean;
creditBalance: number;
entitlements: MachineContext["entitlements"];
}
type UserSnapshot = SnapshotFrom<typeof userMachine>;
type UserSelector<T> = (snapshot: UserSnapshot) => T;
const UserActorContext = createActorContext(userMachine);
export interface UserProviderProps {
children: ReactNode;
}
export function UserProvider({ children }: UserProviderProps) {
return <UserActorContext.Provider>{children}</UserActorContext.Provider>;
}
export function useUserState(): UserState {
return useUserSelector(selectUserState, shallowEqual);
}
export function useUserDispatch(): Dispatch<UserEvent> {
return UserActorContext.useActorRef().send;
}
export function useUserSelector<T>(
selector: UserSelector<T>,
compare?: (previous: T, next: T) => boolean,
): T {
return UserActorContext.useSelector(selector, compare);
}
function selectUserState(state: UserSnapshot): UserState {
return {
currentUser: state.context.currentUser,
isLoading:
state.matches("initializing") ||
state.matches("fetching") ||
state.matches("loggingOut") ||
state.context.isLoading,
avatarUrl: state.context.avatarUrl,
isVip: state.context.isVip,
creditBalance: state.context.creditBalance,
entitlements: state.context.entitlements,
};
}