前言: 大家好我是小瑜,上周五组会,组长安利了一款React强大的状态管理工具:zustand,并且正好看到光哥的手册中有对应的文章,帮助进行了一波研究。相比于Redux,没有复杂的reducer、useSelector、useDispatch等这些概念。类似于Vue的pinia,使用起来非常简便,特别是源码部分非常巧妙,不到100行就可以实现。接下来用简洁的例子来说明zustand的使用、持久化、如何配合immer以及源码的实现。
1. 基本使用
tsx
import React, { useState } from 'react'
import { create } from 'zustand'
const useMyStore = create((set) => ({
aaa: '',
bbb: '',
updateAaa: (value) => set(() => ({ aaa: value })),
updateBbb: (value) => set(() => ({ bbb: value })),
}))
const Ccc = () => {
const { aaa } = useMyStore()
return <p>hello, {aaa}</p>
}
const Bbb = () => {
return (
<div>
<Ccc />
</div>
)
}
const App = () => {
const { aaa, updateAaa } = useMyStore()
return (
<div>
<input onChange={(e) => updateAaa(e.currentTarget.value)} value={aaa} />
<Bbb />
</div>
)
}
export default App
2. subscribe监视器
作用类似于useEffect,会监听到最新的值,页面的值和打印的值同步
tsx
/**
* 监视器
* 回调函数可以拿到当前 state,或者调用 store.getState 也可以拿到 state
*/
useMyStore.subscribe((state) => {
console.log(useMyStore.getState())
if (useMyStore.getState().aaa === 'aaa') {
return setBool(true)
}
return setBool(false)
})
3. 中间件的原理
create 方法的参数,它是一个接受 set、get、store 的三个参数的函数,利用这个特点来前置工作,也就是类似写中间件
这个就是中间件,和redux的中间件是一样的设计。 它并不是zustand本身做啥支持,只要把create的参数设计成一个函数,这个函数接收set、get 等作为参数,那就致残支持了中间件。
注意 此时打印获取的get(),是上一次变化的值,并不是最新的值!
tsx
const logMiddleWare = (callback) => {
return (set, get, store) => {
function newSet(...args) {
console.log('调用了 set, 新的state:', get())
console.log('调用了store', store)
return set(...args)
}
return callback(newSet, get, store)
}
}
const useMyStore = create(
logMiddleWare((set) => ({
aaa: '',
bbb: '',
updateAaa: (value) => set(() => ({ aaa: value })),
updateBbb: (value) => set(() => ({ bbb: value })),
}))
)
zustand内置了一些中间件,比如immer、persist。
4. persist 持久化存储
打印和页面显示一致
tsx
import { create } from 'zustand'
import { persist } from 'zustand/middleware'
const useMyStore = create(
// persist 就是同步 store 数据到 localStorage 的
persist(
(set) => ({
aaa: '',
bbb: '',
updateAaa: (value) => set(() => ({ aaa: value })),
updateBbb: (value) => set(() => ({ bbb: value })),
}),
{
name: 'wty',
}
)
)
5. 自定义中间件可以和persist结合起来
tsx
const logMiddleWare = (callback) => {
return (set, get, store) => {
function newSet(...args) {
console.log('调用了 set, 新的state:', get())
// console.log('调用了store', store)
return set(...args)
}
return callback(newSet, get, store)
}
}
const useMyStore = create(
// persist 就是同步 store 数据到 localStorage 的
logMiddleWare(
persist(
(set) => ({
aaa: '',
bbb: '',
updateAaa: (value) => set(() => ({ aaa: value })),
updateBbb: (value) => set(() => ({ bbb: value })),
}),
{
name: 'wty',
}
)
)
)
6. 配合immer插件
tsx
import { create } from 'zustand'
import { produce } from 'immer'
// 创建状态管理器
const useStore = create((set) => ({
obj: {
name: 'zs',
house: {
city: 'beijing',
area: 100,
},
},
changeHouse: (name) =>
set(
produce((state) => {
console.log(state)
state.obj.house.city = name
})
),
addArea: () =>
set(
produce((state) => {
state.obj.house.area++
})
),
}))
useStore.subscribe((state) => {
console.log(useStore.getState())
})
function TodoList() {
const { obj, changeHouse, addArea } = useStore()
return (
<div>
{JSON.stringify(obj)}
<button onClick={() => changeHouse('大房子')}>Add Todo</button>
<button onClick={() => addArea()}>Add Area</button>
</div>
)
}
export default TodoList
7. 自己手写zushand
tsx
import { useEffect, useState, useSyncExternalStore } from 'react'
const createStore = (createState) => {
// 全局状态容器
let state
// 侦听器
const listeners = new Set()
/**
* 核心是判断输入的新值和旧值,将事件并且添加到监听器中并执行,将最新的值添加到状态容器中管理
* @param {*} partial 就是 () => ({ aaa: value } 或者 { aaa: value })
* @param {*} replace
*/
const setState = (partial, replace) => {
const nextState = typeof partial === 'function' ? partial(state) : partial
if (!Object.is(nextState, state)) {
const previousState = state
console.log(previousState, 'previousState')
if (!replace) {
state =
typeof nextState !== 'object' || nextState === null
? nextState
: Object.assign({}, state, nextState)
} else {
state = nextState
}
// 核心在这里,主要修改了数据,会触发侦听器
listeners.forEach((listener) => listener(state, previousState))
console.log(listeners, 'listeners')
}
}
// 获取全局状态
const getState = () => state
// 每次更新都会触发此函数,新增很删除就会触发新值的更新
const subscribe = (listener) => {
listeners.add(listener)
return () => listeners.delete(listener)
}
// 清空订阅
const destroy = () => {
listeners.clear()
}
const api = { setState, getState, subscribe, destroy }
// 返回zushand对应的三个参数 分别是 set get store
state = createState(setState, getState, api)
return api
}
/**
* 触发渲染
* 判断listeners是否一致,如果不一致则进行重新渲染,然后给状态添加一个随机数
* @param {*} api
* @param {*} selector
* @returns
*/
function useStore(api, selector) {
// 方法二:利用 useSyncExternalStore 触发渲染
function getState() {
return selector(api.getState())
}
return useSyncExternalStore(api.subscribe, getState)
// 方法一:更新状态触发渲染
// const [, forceRender] = useState(0)
// useEffect(() => {
// console.log(api.subscribe, 'subscribe')
// api.subscribe((state, prevState) => {
// console.log({ state, prevState }, 'state, prevState')
// const newObj = selector(state)
// const oldobj = selector(prevState)
// if (newObj !== oldobj) {
// forceRender(Math.random())
// }
// })
// }, [])
// return selector(api.getState())
}
// 入口函数
export const create = (createState) => {
const api = createStore(createState)
const useBoundStore = (selector) => useStore(api, selector)
Object.assign(useBoundStore, api)
return useBoundStore
}
引入手写的方法
tsx
import { create } from './myZustand'
其他代码保持不变
查看效果
完结撒花 ✿✿ヽ(°▽°)ノ✿ ~~~~~