02.状态分形与 Slice
状态分形与 Slice
Slice 模式
import { create, StateCreator } from "zustand";
interface BearSlice {
bears: number;
addBear: () => void;
eatFish: () => void;
}
const createBearSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
BearSlice
> = (set) => ({
bears: 0,
addBear: () => set((state) => ({ bears: state.bears + 1 })),
eatFish: () => set((state) => ({ fishes: state.fishes - 1 })),
});
interface FishSlice {
fishes: number;
addFish: () => void;
}
const createFishSlice: StateCreator<
BearSlice & FishSlice,
[],
[],
FishSlice
> = (set) => ({
fishes: 0,
addFish: () => set((state) => ({ fishes: state.fishes + 1 })),
});
const useBoundStore = create<BearSlice & FishSlice>()((...a) => ({
...createBearSlice(...a),
...createFishSlice(...a),
}));
使用 Props 初始状态
在需要依赖性注入的情况下,例如当一个 store 应该用来自组件的 props 进行初始化时,推荐的方法是使用一个带有 React.context 的 vanilla store。
import { createStore } from "zustand";
interface BearProps {
bears: number;
}
interface BearState extends BearProps {
addBear: () => void;
}
type BearStore = ReturnType<typeof createBearStore>;
const createBearStore = (initProps?: Partial<BearProps>) => {
const DEFAULT_PROPS: BearProps = {
bears: 0,
};
return createStore<BearState>()((set) => ({
...DEFAULT_PROPS,
...initProps,
addBear: () => set((state) => ({ bears: ++state.bears })),
}));
};
Creating a context with React.createContext
import { createContext } from "react";
export const BearContext = createContext<BearStore | null>(null);
Basic component usage
// Provider implementation
import { useRef } from "react";
function App() {
const store = useRef(createBearStore()).current;
return (
<BearContext.Provider value={store}>
<BasicConsumer />
</BearContext.Provider>
);
}
// Consumer component
import { useContext } from "react";
import { useStore } from "zustand";
function BasicConsumer() {
const store = useContext(BearContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
const bears = useStore(store, (s) => s.bears);
const addBear = useStore(store, (s) => s.addBear);
return (
<>
<div>{bears} Bears.</div>
<button onClick={addBear}>Add bear</button>
</>
);
}
Common patterns
Wrapping the context provider
// Provider wrapper
import { useRef } from "react";
type BearProviderProps = React.PropsWithChildren<BearProps>;
function BearProvider({ children, ...props }: BearProviderProps) {
const storeRef = useRef<BearStore>();
if (!storeRef.current) {
storeRef.current = createBearStore(props);
}
return (
<BearContext.Provider value={storeRef.current}>
{children}
</BearContext.Provider>
);
}
Extracting context logic into a custom hook
// Mimic the hook returned by `create`
import { useContext } from "react";
import { useStore } from "zustand";
function useBearContext<T>(
selector: (state: BearState) => T,
equalityFn?: (left: T, right: T) => boolean
): T {
const store = useContext(BearContext);
if (!store) throw new Error("Missing BearContext.Provider in the tree");
return useStore(store, selector, equalityFn);
}
// Consumer usage of the custom hook
function CommonConsumer() {
const bears = useBearContext((s) => s.bears);
const addBear = useBearContext((s) => s.addBear);
return (
<>
<div>{bears} Bears.</div>
<button onClick={addBear}>Add bear</button>
</>
);
}
Complete example
// Provider wrapper & custom hook consumer
function App2() {
return (
<BearProvider bears={2}>
<HookConsumer />
</BearProvider>
);
}