说到react中常用的状态管理工具,你可能会如数家珍,什么redux,mobx,saga,react-redux...
长期以来在react中应用最广泛的redux虽然能够完成各种基本功能,有庞大的插件系统的支持,但是常常会被人诟病使用非常繁琐,上手的心智负担太重,今天要介绍的zustand可以完美的解决这个问题。使用简洁丝滑,易于理解,你值得拥有。
基本使用
先来了解一下如何使用,打开zustand官网:
一个大大的猪四蛋坐在树桩上跟着鼠标来回晃动,一股非常高端气息扑面而来,zustand 在我心目中的逼格瞬间高大起来...
不错。不错。
首先使用npm安装一下:
js
npm install zustand
根据官网中给出的例子,创建一个仓库文件store:
js
import { create } from 'zustand'
const useStore = create((set) => ({
bears: 0,
increase: () => set((state) => ({ bears: state.bears + 1 })),
remove: () => set({ bears: 0 }),
update: (newBears) => set({ bears: newBears }),
}))
create
函数创建一个仓库,接收一个函数,其中默认传入set
函数,通过set
函数更新state
的值。
state
和修改 state
的方法都在create
中被定义。在set
中提供state
便于对值进行修改。
然后呢?
这就结束了。
使用的时候也很简单,在组件中引入store
的返回值,自定义函数获取想要的state
或者更新函数。
js
import { useStore } from '../store'
// 获取state用于显示在页面中
function BearCounter() {
const bears = useStore((state) => state.bears)
return <h1>{bears} around here...</h1>
}
// 获取更新函数更新state
function Controls() {
const increasePopulation = useStore((state) => state.increase)
return <button onClick={increasePopulation}>one up</button>
}
zustand支持监听器,当数据变动时,执行收集的监听函数。
使用useStore
获取state
:
js
useStore.subscribe((state) => {
console.log(useStore.getState());
})
插件体系
zustand与redux一样,也支持插件体系。但是并不是zustand实现的功能,他只是把你传入的函数执行了一遍而已,同时会提供state
。
js
const useStore = create((set) => ({}))
在创建store
时,会提供set、get、store 的三个参数,这样就可以使用zustand提供的这三个参数做一些额外的处理:
js
function miniMiddleware(fn) {
return function(set, get, store) {
function todo(...args) {
//...
// 你想做的事
}
return fn(todo, get, store)
}
}
使用插件时创建store
可以包裹使用:
js
const useStore = create(miniMiddleware((set) => ({})))
使用
基本概念了解了,接下来趁热打铁,用起来!用 zustand实现一个简单入门乞丐版的 todoList。
对了对比,先使用普通的 state 方式来实现:
js
import { FC, useState } from 'react'
import { create } from 'zustand'
const List: FC = () => {
function changeVal(e) {
setVal(e.target.value)
}
const [val, setVal] = useState('')
const [list, setList] = useState<Array<string>>([])
function addItem() {
setList([
...list,
val
])
setVal('')
}
function deleteItem(index: number) {
let newList = list
newList.splice(index, 1)
setList([
...newList
])
}
return <div>
<input type="text" value={val} onChange={(e) => changeVal(e)} />
<button onClick={addItem}>++</button>
<div>
{
list.map((c, i) => (
<div key={i}>
{c}
<button onClick={() => deleteItem(i)}>delete</button>
</div>
))
}
</div>
</div>
}
export default List
接下来使用 zustand 改写,将列表放入到 store 中:
js
import { FC, useState } from 'react'
import { create } from 'zustand'
const useStore = create((set) => ({
list: [],
add: (val) => {
set(state => {
return {
list: [
...state.list,
val
]
}
})
},
delete: (val) => {
set(state => {
return {
list: state.list.filter(item => item !== val)
}
})
},
}))
const List: FC = () => {
function changeVal(e) {
setVal(e.target.value)
}
const [val, setVal] = useState('')
const list = useStore((state) => state.list)
const add = useStore((state) => state.add)
const deleteItem = useStore((state) => state.delete)
function addItem() {
add(val)
setVal('')
}
return <div>
<input type="text" value={val} onChange={(e) => changeVal(e)} />
<button onClick={addItem}>++</button>
<div>
{
list.map((c, i) => (
<div key={i}>
{c}
<button onClick={() => deleteItem(c)}>delete</button>
</div>
))
}
</div>
</div>
}
export default List
大功告成!
实现
根据我们上面的用法可以看出来,create
函数主要是创建仓库,而返回值用来获取state
或者获取更新函数。
js
const create = (createState) => {
// 创建store并返回一系列的功能函数
const api = createStore(createState)
// selector就是开发者将来传入的获取state内部值的函数
const useBoundStore = (selector) => useStore(api, selector)
Object.assign(useBoundStore, api);
return useBoundStore
}
- createStore
replace
是 zustand 在 set 状态的时候默认是合并 state 操作,也可以传一个 true 改成替换。
总共分为以下几个步骤:
-
开发者传入的更新的参数是否为函数?
- 如果是函数则将state传入并执行函数,如果不是直接返回值
-
与上次更新的值是否一致?(判断多次更新值是否发生了变化,优化操作)
-
通过
replace
对state进行不同的操作:合并/替换 -
还有一些其他功能:
getState
返回 state
js
const createStore = (createState) => {
let state;
// 更新
const setState = (partial, replace) => {
// 是否为函数?
const nextState = typeof partial === 'function' ? partial(state) : partial
// 值是否发生了变化?
if (!Object.is(nextState, state)) {
const previousState = state;
// 对state进行何种操作?
if(!replace) {
// 合并
state = (typeof nextState !== 'object' || nextState === null)
? nextState
: Object.assign({}, state, nextState);
} else {
// 覆盖
state = nextState;
}
}
}
const getState = () => state;
const api = { setState, getState, destroy }
// 初始化
state = createState(setState, getState, api)
return api
}
在此基础上实现subscribe
就很简单了,当调用subscribe
时手收集函数,state发生变动时,执行一遍。
同时提供一个销毁函数,用于将订阅函数进行手动销毁:
diff
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;
if(!replace) {
state = (typeof nextState !== 'object' || nextState === null)
? nextState
: Object.assign({}, state, nextState);
} else {
state = nextState;
}
++ listeners.forEach((listener) => listener(state, previousState));
}
}
const getState = () => state;
// 收集订阅函数
++ const subscribe= (listener) => {
++ listeners.add(listener)
++ return () => listeners.delete(listener)
++ }
// 手动销毁
++ const destroy= () => {
++ listeners.clear()
++ }
const api = { setState, getState, subscribe, destroy }
// 初始化
state = createState(setState, getState, api)
return api
}
接下来就是需要执行开发者传入的函数,也就是这部分操作:
状态更改,触发渲染。可以使用useState
触发一个随机数的方式:
js
function useStore(api, selector) {
// 定义setState
const [,forceRender ] = useState(0);
useEffect(() => {
// 使用订阅的方式,每次state变更都会触发重新渲染
api.subscribe((state, prevState) => {
const newObj = selector(state);
const oldobj = selector(prevState);
// 是否需要重新渲染?
if(newObj !== oldobj) {
forceRender(Math.random());
}
})
}, []);
return selector(api.getState());
}
监听 state 的变化,变了之后,根据新旧 state
调用 selector
函数的结果,来判断是否需要重新渲染。
对于触发重新渲染的方式,react提供一个hook,专门用于处理这一类的事件:
useSyncExternalStore
是一个hook,用来定义外部 store
,store
变化以后会触发 重新渲染。
参数subscribe
:一个函数,接收一个单独的 callback
参数并把它订阅到 store
上。当 store
发生改变,它应当调用被提供的 callback
。这会导致组件重新渲染。subscribe
函数会返回清除订阅的函数。
通过useSyncExternalStore
可以很方便的实现当 store
中的state
改变,重新渲染组件。
所以可以改写成这样:
js
function useStore(api, selector) {
function getState() {
return selector(api.getState());
}
return useSyncExternalStore(api.subscribe, getState)
}
最后完整代码:
js
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;
if(!replace) {
state = (typeof nextState !== 'object' || nextState === null)
? nextState
: Object.assign({}, state, nextState);
} else {
state = nextState;
}
listeners.forEach((listener) => listener(state, previousState));
}
}
const getState = () => state;
const subscribe= (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
const destroy= () => {
listeners.clear()
}
const api = { setState, getState, subscribe, destroy }
// 初始化
state = createState(setState, getState, api)
return api
}
function useStore(api, selector) {
function getState() {
return selector(api.getState());
}
return useSyncExternalStore(api.subscribe, getState)
}
export const create = (createState) => {
const api = createStore(createState)
const useBoundStore = (selector) => useStore(api, selector)
Object.assign(useBoundStore, api);
return useBoundStore
}
The End。
写在最后 ⛳
未来可能继续输出antd
源码解析系列文章,希望能一直坚持下去,期待多多点赞🤗🤗,一起进步!🥳🥳