前言
说起 Zustand,很多使用 React 的小伙伴肯定不陌生
它是一个 React 状态管理库,类似于 Redux、Mobx
一起来看下 Zustand 的周下载量,大概在 250w,妥妥的第一梯队
再来看下 Zustand 的 github 标星,截止目前(2024-3-27)已经有 41.6k,妥妥的高赞仓库
如此优秀的 React 状态管理库不得不研究下。
顺便说一下,Zustand 的作者写了三个状态管理库,凭借一己之力,搅混 React 状态管理库的水。就是下面这哥们:
三个状态管理库,三种不同的思想,不得不说一句:真牛!!!,有兴趣的同学可以访问以下链接查看:
作者 Github: github.com/dai-shi
基本使用
看下官网提供的例子:
导入 React 项目中:
javascript
import { create } from 'zustand';
type Store = {
count: number;
inc: () => void;
};
// 定义store及方法
const useStore = create<Store>(set => ({
count: 1,
inc: () => set(state => ({ count: state.count + 1 })),
}));
export default useStore;
javascript
import './App.css';
import useStore from './store';
function App() {
// 在组件中使用
const { count, inc } = useStore();
return (
<div>
<h1>{count}</h1>
<button onClick={inc}>one up</button>
</div>
);
}
export default App;
查看下运行结果:
如果你不喜欢将方法和数据定义在一个对象中,zustand 还支持如下写法:
javascript
import { create } from 'zustand';
type Store = {
count: number;
};
// store 中自定义数据
export const useStore = create<Store>(set => ({
count: 1,
}));
// 在store外定义修改函数
export const inc = () => {
useStore.setState(state => ({ count: state.count + 1 }));
};
// 在store外部获取store内容
export const getStore = () => {
return useStore.getState();
}
这意味着用户可以在 React 组件外获取和修改 store 的数据,这在有些情况下是非常有用的。
此外,Zustand 还支持中间价,官方提供了几个常用的中间件,我们以 persist
持久话缓存为例,看下使用方式:
javascript
import { create } from 'zustand';
import { persist } from 'zustand/middleware';
type Store = {
count: number;
};
export const useStore = create(
persist<Store>(
() => ({
count: 1,
}),
{
name: 'zustand',
},
),
);
export const inc = () => {
useStore.setState(state => ({ count: state.count + 1 }));
};
看下效果:
以上就是 Zustand 的全部使用方法,是不是很简单,而这就是他迅速占领 React 状态管理第一梯队的原因;
Zustand 使用起来这么简单,那它的源码一定不简单吧!
非也,让我们一起来解开它神秘的面纱。
深入源码
create
函数
首先创建 store 会先调用 Zustand 的 create
函数,就先从它入手(注意以下代码去掉了类型和一些提示类代码):
javascript
const createImpl = (createState) => {
// 获取api
const api = typeof createState === 'function' ? createStore(createState) : createState
// 获取useBoundStore
const useBoundStore = (selector, equalityFn) => useStore(api, selector, equalityFn)
// 将useBoundStore于api合并
Object.assign(useBoundStore, api)
// 返回 useBoundStore
return useBoundStore
}
export const create = (createState) => createImpl(createState);
api 是个什么东西?
useBoundStore 又是个什么东西?
有点云里雾里的感觉,重点是 函数 createStore
和 useStore
的执行结果。一个一个看:
createStore
函数
首先是 createStore
函数:
javascript
const createStoreImpl = 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 destroy = () => {
listeners.clear();
};
const api = { setState, getState, getInitialState, subscribe, destroy };
const initialState = (state = createState(setState, getState, api));
return api;
};
export const createStore = createState => createStoreImpl(createState);
分析下:
createStore
函数执行其实就是createStoreImpl
执行,最后返回了一个对象api
,api
中保存有setState, getState, getInitialState, subscribe, destroy
等函数setState
函数是主要的改变数据的函数,仔细看下。- 首先
nextState
用于获取变化后的值 - 之后使用
Object.is()
比较新旧state
,如果不相同则将新值与旧值执行替换或是合并操作(这里需要注意下:Zustand 支持设置replace
变量,true
表示 直接使用新值替换旧值,false
表示将新旧值合并,相同属性进行覆盖,新值中没有的属性进行保留,具体使用说明参见 Zustand状态合并) - 最后执行
listeners
集合中全部的listener
,这个listener
是个啥东西,现在还不清楚,咱们继续往下看
- 首先
getState
函数很简单,直接返回state
getInitialState
函数也很简单,返回初始的state
subscribe
函数 用于添加自定义的监听函数destroy
函数用于清除全部的监听函数- 最后调用用户初始传入的
createState
函数获取初始的state
值
useStore
函数
下面看下useStore
函数:
javascript
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
函数,这个函数是 use-sync-external-store/shim/with-selector
包中的一个函数。
其实useSyncExternalStoreWithSelector
就是对 useSyncExternalStore
的一个包装,那useSyncExternalStore
是个啥东西:
useSyncExternalStore
useSyncExternalStore
是 React 18 中提供的一个新的 hook,主要是用于订阅和读取外部的值。
使用方法 :
useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot?)
参数:
subscribe
:一个函数,接收一个单独的callback
参数并把它订阅到 store 上。当 store 发生改变,它应当调用被提供的callback
。这会导致组件重新渲染。subscribe 函数会返回清除订阅的函数。这也就解释了刚才我们的疑惑:listener
是个啥东西?listener
其实就是触发 React 重新渲染的函数以及我们自己定义的监听数据变化后做的副作用函数。getSnapshot
:一个函数,返回组件需要的 store 中的数据快照。在 store 不变的情况下,重复调用getSnapshot
必须返回同一个值。如果 store 改变,并且返回值也不同了(用Object.is
比较),React 就会重新渲染组件。- **可选 **
getServerSnapshot
:一个函数,返回 store 中数据的初始快照。它只会在服务端渲染时,以及在客户端进行服务端渲染内容的 hydration 时被用到。快照在服务端与客户端之间必须相同,它通常是从服务端序列化并传到客户端的。如果你忽略此参数,在服务端渲染这个组件会抛出一个错误。
返回值 :
该 store 的当前快照,可以在你的渲染逻辑中使用。
具体参考 React 文档中 useSyncExternalStore 的介绍
useSyncExternalStoreWithSelector
是在useSyncExternalStore
的基础上添加了两个参数:
selector
:一个函数,用于获取state
中的部分数据,有了这个参数useSyncExternalStoreWithSelector
的返回值就可以根据selector
的结果来返回而不是每次都返回整个 store,相对灵活方便equalityFn
:数据比较方法,如果不希望使用Object.is
做数据对比,可以提供自己的对比函数
Zustand 也正是通过这个 hook 实现数据变化更新视图的。
回到 create
函数
函数 createStore
和 useStore
的内部原理我们都已经解释清楚,下面回到最开始的 create
函数:
javascript
const createImpl = (createState) => {
// 获取api
const api = typeof createState === 'function' ? createStore(createState) : createState
// 获取useBoundStore
const useBoundStore = (selector, equalityFn) => useStore(api, selector, equalityFn)
// 将useBoundStore于api合并
Object.assign(useBoundStore, api)
// 返回 useBoundStore
return useBoundStore
}
export const create = (createState) => createImpl(createState);
这下让我们再分析下:
api
就是存放着setState, getState, getInitialState, subscribe, destroy
函数的一个对象useBoundStore
是一个函数,它调用之后会根据传入的selector
返回对应的 store 数据,如果没有传入selector
,则会返回整个 storeObject.assign(useBoundStore, api)
将useBoundStore
函数和api
进行合并,目的就是方便用户使用如下方式修改和获取数据,即在 React 组件之外修改和获取数据
javascript
export const inc = () => {
useStore.setState(state => ({ count: state.count + 1 }));
};
// 在store外部获取store内容
export const getStore = () => {
return useStore.getState();
}
总结
本文主要讲解了 Zustand 的使用和源码实现,原来,Zustand 不仅使用起来简单,源码更简单。
希望本文能帮助你更好的使用 Zustand。
我是克鲁,我们下期再见。
彩蛋:下期是不是得重点分析下 _useSyncExternalStore_
的源码呢?如果你有这个需求,请在评论区留下你的足迹,让我知道有多少人有这个需求。