zustand的使用
js
import { create } from 'zustand';
type Store = {
count: number;
add: () => void;
};
// 定义store及方法
const testStore = create<Store>(set => ({
count: 1,
add: () => set(state => ({ count: state.count + 1 })),
}));
export default testStore;
jsx
import useStore from './store';
function App() {
// 在组件中使用
const { count, add } = testStore();
return (
<div>
<h1>{count}</h1>
<button onClick={add}>one up</button>
</div> )
}
export default App;
进入源码
1、先从create函数开始
在使用的时候,我们调用了creat方法,去创建一个store,发现其实就是去执行了createImple
进入createImpl
ts
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
const api =
typeof createState === 'function' ? createStore(createState) : createState
const useBoundStore: any = (selector?: any, equalityFn?: any) =>
useStore(api, selector, equalityFn)
Object.assign(useBoundStore, api)
return useBoundStore
}
简化代码之后,发现就是去执行了createStore和useStore两个方法,分别返回了一个api对象和一个useBoundStore对象,并且将这两个对象合并,最终返回了useBoundStore对象
接下来分别进入createStore方法和useStore方法
进入createStore方法
createStore方法 是去执行的createStoreImpl方法,go on
进入createStoreImpl方法
ts
const createStoreImpl: CreateStoreImpl = (createState) => {
let state: TState
const listeners: Set<Listener> = new Set()
const setState: StoreApi<TState>['setState'] = (partial, replace) => {
const nextState =
typeof partial === 'function'
? (partial as (state: TState) => TState)(state)
: partial
if (!Object.is(nextState, state)) {
const previousState = state
state =
replace ?? (typeof nextState !== 'object' || nextState === null)
? (nextState as TState)
: Object.assign({}, state, nextState)
listeners.forEach((listener) => listener(state, previousState))
}
}
const getState: StoreApi<TState>['getState'] = () => state
const getInitialState: StoreApi<TState>['getInitialState'] = () =>
initialState
const subscribe: StoreApi<TState>['subscribe'] = (listener) => {
listeners.add(listener)
// Unsubscribe
return () => listeners.delete(listener)
}
const destroy: StoreApi<TState>['destroy'] = () => {
if (import.meta.env?.MODE !== 'production') {
console.warn(
'[DEPRECATED] The `destroy` method will be unsupported in a future version. Instead use unsubscribe function returned by subscribe. Everything will be garbage-collected if store is garbage-collected.',
)
}
listeners.clear()
}
const api = { setState, getState, getInitialState, subscribe, destroy }
const initialState = (state = createState(setState, getState, api))
return api as any
}
删减代码之后发现,原来使用的setState,getState等方法是通过createStoreImpl这里来的, 也就是说通过createStoreImpl创建的api对象包含了setState, getState, getInitialState, subscribe, destroy这几个方法
- setState: 用来更改数据状态,通过Object.is来判断是否决定更改,如果相同则进行数据更新,最后调用listener,这个listeners里面的每一个listener是啥,先待定。
- getState:就是用来获取状态的
- getInitialState:获取初始的state
- subscribe:这里也用到listeners了,发现这个subscribe就是用来添加自定义的监听函数用的
- destroy:执行listeners.clear() 说明这个函数 是用来清除所有监听的
继续useStore
ts
import useSyncExternalStoreExports from 'use-sync-external-store/shim/with-selector'
const { useSyncExternalStoreWithSelector } = useSyncExternalStoreExports;
const identity = arg => arg;
export function useStore(api, selector = identity, equalityFn) {
const slice = useSyncExternalStoreWithSelector(
api.subscribe,
api.getState,
api.getServerState || api.getInitialState,
selector,
equalityFn,
);
return slice;
}
发现这个useStore去执行了个useSyncExternalStoreWithSelector,不清楚这个是干啥的,go on
进入useSyncExternalStoreWithSelector
发现是从这个包导入进来的,去扒一下这个包,
原来是对useSyncExternalStore这个的封装,所以还是不知道useSyncExternalStore这个是个啥
查一下useSyncExternalStore
js
import { useSyncExternalStore } from 'react';
import { todosStore } from './todoStore.js';
function TodosApp() {
const todos = useSyncExternalStore(todosStore.subscribe, todosStore.getSnapshot);
}
原来这个是用来订阅外部数据以及读取外部数据的东西,接收三个参数,其中第三个是可选的
-
subscribe
:一个函数,接收一个单独的callback
参数并把它订阅到 store 上。当 store 发生改变,它应当调用被提供的callback
。这会导致组件重新渲染。subscribe 函数会返回清除订阅的函数。这也就解释了刚才我们的疑惑:listener
是个啥东西?listener
其实就是触发 React 重新渲染的函数以及我们自己定义的监听数据变化后做的副作用函数。 -
getSnapshot
:一个函数,返回组件需要的 store 中的数据快照。在 store 不变的情况下,重复调用getSnapshot
必须返回同一个值。如果 store 改变,并且返回值也不同了(用Object.is
比较),React 就会重新渲染组件。 -
**可选 **
getServerSnapshot
:一个函数,返回 store 中数据的初始快照。它只会在服务端渲染时,以及在客户端进行服务端渲染内容的 hydration 时被用到。快照在服务端与客户端之间必须相同,它通常是从服务端序列化并传到客户端的。如果你忽略此参数,在服务端渲染这个组件会抛出一个错误。
useSyncExternalStoreWithSelector在这个基础上,增加了2个参数,一个是selector,用于获取state的部分数据,useSyncExternalStoreWithSelector可以根据selector的结果返回对应的值,而不是每次都必须要返回整个store,另一个是equalityFn,可以提供自定义的比较方法,如果你不希望用刚才的那个object.is,那你就用它
返回create
js
const createImpl = <T>(createState: StateCreator<T, [], []>) => {
const api =
typeof createState === 'function' ? createStore(createState) : createState
const useBoundStore: any = (selector?: any, equalityFn?: any) =>
useStore(api, selector, equalityFn)
Object.assign(useBoundStore, api)
return useBoundStore
}
重新分析一下,create方法就是createImpl方法的执行,返回一个useBoundStore这个方法,它是由createStore和useStore的返回值组装的,分别对应着api和useBoundStore,api这个对象存放着一系列的方法比如setState、getState等供我们调用,useBoundStore如果收到selector传参,则返回对应的store数据,如果没有就返回整个store
总结
Zustand 是一个为 React 应用设计的状态管理库,它通过提供一个简单且高效的 API 来管理应用的全局状态。它允许定义多个独立的 store,每个 store 可以包含自己的状态和更新逻辑。 组件订阅这些状态,当状态更新的时候,组件会重新渲染,这背后主要是基于react18新提供的apiuseSyncExternalStore,通过subsribe监听state状态,当state更新时候,触发订阅者更新,获取最新状态是通过getSnapshot。