findxc / blog

88 stars 5 forks source link

试了一下 Zustand ,一个挺简洁的状态管理库 #81

Open findxc opened 1 year ago

findxc commented 1 year ago

官方文档: https://docs.pmnd.rs/zustand/getting-started/introduction

我的感受

优点:

BAB12944-FEAE-4170-BC1B-9BC661A4FFEC

因为这个库我去学了 TS 哈哈哈,以前只会定义 interface ,现在会一点类型推导了 ✌️

实际场景一:怎么 reset 所有 store 的值

比如退出登录时希望重置所有 store 值为初始状态。参见 https://docs.pmnd.rs/zustand/guides/how-to-reset-state 二次封装一下 create 即可。

实际场景二:可以根据 initialState 来自动生成对应的 actions 函数吗

import { create } from 'zustand'

interface BearState {
  bears: number
  setBears: (value: number) => void
}

const useBearStore = create<BearState>(set => ({
  bears: 0,
  setBears: value => set({ bears: value }),
}))

比如上面的代码, BearState 那里我需要写 setBears 的 TS 定义,在 create 时我也需要写 setBears 的具体实现。我们能不能自动生成相应的 setState: value => set({ state: value })

setBears 的 TS 定义可以通过推导来得到:

type Actions<T> = {
  [P in keyof T & string as `set${Capitalize<P>}`]: (value: T[P]) => void
}

Actions<{ bears: number }> 结果如下:

5F3BAF1F-961E-4FCB-BE5D-1870E8587CC4

setBears 这个函数也是可以根据 { bears: 0 } 来类似生成的。

封装后的 create 完整代码如下:

// create.ts
import { create as _create } from 'zustand'

type Actions<T> = {
  [P in keyof T & string as `set${Capitalize<P>}`]: (value: T[P]) => void
}

const resetters: (() => void)[] = []

const create = <State, A = object>(initialState, extraActions?) => {
  const store = _create<State & Actions<State> & A>((set, get) => ({
    ...initialState,
    // 根据 initialState 自动生成对应的 actions
    ...Object.keys(initialState).reduce((total, key) => {
      const functionName = `set${key[0].toUpperCase()}${key.slice(1)}`
      // @ts-ignore
      total[functionName] = value => set({ [key]: value })
      return total
    }, {}),
    // 也支持传入自定义的 actions
    ...(extraActions ? extraActions(set, get) : {}),
  }))

  resetters.push(() => {
    store.setState(initialState)
  })

  return store
}

export const resetAllStores = () => {
  for (const resetter of resetters) {
    resetter()
  }
}

export default create

一个使用例子:

// useGlobalStore.ts
import create from './create'

type State = {
  name: string
  permissions: string[]
}

const initialState: State = {
  name: '',
  permissions: [],
}

const extraActions = set => ({
  updateUser: (user: Pick<State, 'name' | 'permissions'>) => set(user),
})

const useGlobalStore = create<State, ReturnType<typeof extraActions>>(
  initialState,
  extraActions
)

export default useGlobalStore

useGlobalStore 使用时的类型提示如下:

B6E89207-F0D9-44A8-9AEF-E7333352B5D0

setNamesetPermissions 的 TS 定义和函数实现是自动生成的,然后我们也支持在 create 时传入 updateUser 这样的自定义 actions 。

和 Valtio 的使用方式的区别

Valtio 也是一个状态管理库,文档见 https://github.com/pmndrs/valtio

一个简单例子如下:

import { proxy, useSnapshot } from 'valtio'
const state = proxy({ count: 0, text: 'hello' })

function Counter() {
  const snap = useSnapshot(state)
  return (
    <div>
      {snap.count}
      <button onClick={() => ++state.count}>+1</button>
    </div>
  )
}

我不太喜欢 ++state.count 这种直接去改状态的方式,我更喜欢通过一个函数去更新状态。这样当我希望知道哪些地方有改动某个状态时,我全局搜索对应的函数就行了, ++state.count 这种方式不利于定位代码。

hyoban commented 1 year ago

最喜欢 jotai,用起来和 useState 没差。有个一小差别,zustand 的类型需要自己通过泛型标,jotai 不用