实现
一、认识
Zustand
相较于 React-Redux
的优势在于其极简且直观的 API
设计,无需繁琐的 action
、reducer
和中间件配置, 无需繁杂的样板代码, 通过闭包和订阅机制实现了精准更新, 确保组件只在真正需要时才重新渲染, 同时支持状态切片和选择器,使得组件能够只订阅所需数据。再加上与 React 18
的 useSyncExternalStore
无缝衔接, 能够在并发模式下保持数据一致性和高性能, 借助 useSyncExternalStore
与选择器使组件仅订阅所需状态,从而减少不必要的重渲染。同时它不依赖于 React Context
,避免了大规模状态更新时的性能瓶颈,使用起来更符合现代 React Hooks
编程风格,学习曲线更低,适用于从小型到大型应用,而 Redux
尽管拥有丰富的生态系统和强大的中间件支持,但往往需要更多样板代码和复杂的架构来管理状态。
useSyncExternalStore
是 React 18
引入的一个内置 Hook
, 专门用于解决外部数据源(比如 Redux``、Zustand
等状态管理库)与 React
组件之间的数据同步问题。它的主要作用是让组件在渲染期间能够安全、一致地读取外部 store
的状态快照, 同时在 store
发生变化时,自动触发组件重新渲染,从而确保 UI
始终与外部状态保持同步。具体来说,它需要你提供两个核心函数:
-
subscribe
: 一个函数, 接收一个单独的callback
参数并把它订阅到store
上。当store
发生改变时会调用提供的callback
,这将导致React
重新调用getSnapshot
并在需要的时候重新渲染组件。subscribe
函数会返回清除订阅的函数。换句话说:subscribe
的作用就是让每个使用store
的组件都能注册自己的订阅回调。当store
更新时, 我们手动调用订阅回调, 从而让相应的组件通过getSnapshot
获取最新状态并触发重新渲染。所以subscribe
注册的回调数目实际上就反映了当前有多少组件(或其他订阅者)在关注这个store
的变化,同时它也支持取消订阅以避免不必要的更新。 -
getSnapshot
: 一个函数,返回组件需要的store
中的数据快照。在store
不变的情况下,重复调用getSnapshot
必须返回同一个值。如果store
改变,并且返回值也不同了(用Object.is
比较),React
就会重新渲染组件。 -
返回值: 该
store
的当前快照,可以在你的渲染逻辑中使用。
Zustand
的 create
实现逻辑基于 useSyncExternalStore
, 通过 subscribe
参数来收集所有订阅该 Store
的组件的通知更新回调。通过 getSnapshot
来传递给 store
当前数据状态。当修改 store
数据后, 会循环遍历订阅 Store
的组件通知更新回调, React
会重新调用 getSnapshot
, 并通过 Object.is
来比较前后状态, 如果发生变化, 在需要的时候重新渲染组件。因此, Zustand
的 create
通过 useSyncExternalStore subscribe 与 getSnapshot
的结合, 实现组件对外部状态的精准订阅和更新, 从而构建出一个轻量高效且易于扩展的状态管理解决方案。
二、实现
import { useSyncExternalStore } from "react";
const createStore = (createState) => {
let state;
const listeners = new Set();
const setState = (partial, replace) => {
const nextState = typeof partial === "function" ? partial(state) : partial;
if (!Object.is(nextState, state)) {
const previousState = state;
state =
replace ?? (typeof nextState !== "object" || nextState === null)
? nextState
: Object.assign({}, state, nextState);
listeners.forEach((listener) => listener(state, previousState));
}
};
const getState = () => state;
const getInitialState = () => initialState;
const subscribe = (listener) => {
listeners.add(listener);
return () => listeners.delete(listener);
};
const api = { setState, getState, getInitialState, subscribe };
const initialState = (state = createState(setState, getState, api));
return api;
};
function useStore(api, selector) {
const slice = useSyncExternalStore(
api.subscribe,
() => selector(api.getState()),
() => selector(api.getInitialState())
);
return slice;
}
function create(createState) {
const api = createStore(createState);
const useBoundStore = (selector) => useStore(api, selector);
Object.assign(useBoundStore, api);
return useBoundStore;
}
三、测试
import { create } from "./create";
const useCountStore = create((set, get) => ({
count: 0,
setCount: (count) => set({ count }),
increment: () => set((state) => ({ count: state.count + 1 })),
decrement: () => set((state) => ({ count: state.count - 1 })),
}));
export default useCountStore;
import useCountStore from "./store/zustand/index1";
function App() {
const count = useCountStore((state) => state.count);
const increment = useCountStore((state) => state.increment);
const decrement = useCountStore((state) => state.decrement);
return (
<div>
<p>Count: {count}</p>
<button onClick={increment}>Increase</button>
<button onClick={decrement}>Decrease</button>
</div>
);
}
export default App;