强大的 zustand 从使用到源码

前言:
大家好我是小瑜,上周五组会,组长安利了一款React强大的状态管理工具:zustand,并且正好看到光哥的手册中有对应的文章,帮助进行了一波研究。相比于Redux,没有复杂的reducer、useSelector、useDispatch等这些概念。类似于Vue的pinia,使用起来非常简便,特别是源码部分非常巧妙,不到100行就可以实现。接下来用简洁的例子来说明zustand的使用、持久化、如何配合immer以及源码的实现。

1. 基本使用

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,会监听到最新的值,页面的值和打印的值同步

强大的 zustand 从使用到源码

/**
 * 监视器
 * 回调函数可以拿到当前 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(),是上一次变化的值,并不是最新的值!
强大的 zustand 从使用到源码

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 持久化存储

打印和页面显示一致
强大的 zustand 从使用到源码

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结合起来

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插件

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

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
}

引入手写的方法

import { create } from './myZustand'
其他代码保持不变

查看效果
强大的 zustand 从使用到源码

完结撒花 ✿✿ヽ(°▽°)ノ✿ ~~~~~

原文链接:https://juejin.cn/post/7346911517390192690 作者:不知名小瑜

(0)