React — zustand状态管理工具

Jiafeng

分类: React 18 0

本文转自 https://juejin.cn/post/7388064351504056335,如有侵权,请联系删除。

Zustand 是目前生态系统中非常流行的一款状态管理库,相比经典的 Redux,学习成本极低,使用超简单,今天就来介绍。

快速开始

创建项目

bash 体验AI代码助手 代码解读复制代码npm create vite@latest zustand-demo -- --template react
cd zustand-demo
# VS Code 打开
code .
npm install zustand
npm install

快速使用 Zustand

删除 src/index.css 中的内容,将 src/App.jsx 中的内容替换如下:

```jsx
jsx 体验AI代码助手 代码解读复制代码import { create } from 'zustand'

const useStore = create(() => ({
who: 'World',
}))

function App() {
const who = useStore(state => state.who)

return (
<>
Hello {who}!
</>
)
}

export default App


 以上,我们就写了一个最小化的 Zustand 应用了。

 1. 首先,我们向 Zustand 暴露出来的 create() API 中传入了一个回调函数
1. 接着,回调函数返回了一个对象 { who: 'World' },这就是状态对象了,那如何用呢?这就德通过返回值了
1. 对,没错,create() 会返回一个 React Hook,通常我们会叫它,useStore() 或者是根据具体业务场景而命名的 useXXStore()
1. 最后,调用 useStore() 时,Zustand 会将返回的状态传入,由此你可解构出你需要的部分返回。本例中,我们就从 Store 中解构出了 who 这个状态属性

 浏览器访问 [http://localhost:5173/](https://link.juejin.cn/?target=http%3A%2F%2Flocalhost%3A5173%2F) 查看:

 ![](https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/bab75951713c4a3985842189b0ec78a0~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=604&h=172&s=8244&e=png&b=fefefe)

 发现,值为 'World'who 状态被成功渲染出来了。

 ## 更新状态

 当然除了存储状态,Zustand 还支持我们修改状态,在以上案例的基础之上,我们需要修改 2 处。

 ```jsx
jsx 体验AI代码助手 代码解读复制代码const useStore = create((set /* 1 */) => ({
  who: 'World',
  setWho: (who) => set({ who }) /* 2 */
}))
  1. 首先,在调用 create() API 的时候,Zustand 还会额外传入一个 set 参数,它是一个函数,用于设置状态

    1. create() 回调函数中除了能返回状态,还能返回操作状态的函数。本例中新增了一个 setWho() 函数,用于修改状态 who

    接下来,稍稍修改 App 组件。

    ```jsx
    jsx 体验AI代码助手 代码解读复制代码function App() {
    const who = useStore(state => state.who)
    const setWho = useStore(state => state.setWho) / 1 /

    return (
    <>
    <p>Hello {who}!</p>
    <button onClick={() => setWho('Zustand') / 2 /}>Say Hi to Zustand</button>
    </>
    )
    }

  2. 引入状态修改函数 setWho()

    1. 增加按钮,点击时调用 setWho('Zustand'),将状态 who 更新为 'Zustand'

    来看效果:

    发现状态被成功修改了。

    除此之外,Zustand 还天然支持局部状态更新——就是说如果你的 Store 对象中包含不止一个属性时,你也只需要更新你关心的状态即可,其他状态会自动保持原样。

    举个例子。上面的例子,现在除了 who,还有一个 count。

    diff 体验AI代码助手 代码解读复制代码const useStore = create((set) => ({
    + count: 0,
    who: 'World',
    setWho: (who) => set({ who })
    }))

    上述的,setWho() 无需改变,保持 set({ who }) 即可,没被设置的 count 依然保持原样。

    ```diff
    diff 体验AI代码助手 代码解读复制代码- <p>Hello {who}!</p>

    • <p>Hello {who}!(<strong>{useStore(state => state.count)}</strong>)</p>

    查看效果:

    发现 who 被成功更新的同时,count 状态也保留着,这就很棒了。

    同样的更新方式,换成 useState(),我们就得这么做:

    jsx 体验AI代码助手 代码解读复制代码const [store, setStore] = useState({ who: 'World', count: 0 })
    const setWho = (who) => {
    setStore((prevStore) => ({
    ...prevStore,
    who: 'Zustand'
    }))
    }

    看看,是不是复杂很多,这就是使用 Zustand 的好处。

    另外,set() 函数还支持回调函数更新形式,回调函数会接受当前的状态对象,这样就可以基于以前的状态进行更新了。

    以以下新建的 useCountStore 为例:

    ```jsx
    jsx 体验AI代码助手 代码解读复制代码const useCountStore = create((set) => ({
    count: 0,
    inc: () => set((state) => ({ count: state.count + 1 })),
    }))

    
    更新 count 状态的 inc() 函数内部就是通过在之前 state.count 之上加 1 的方式实现计数增加的。
    
    App 组件修改如下:
    
    ```jsx
    jsx 体验AI代码助手 代码解读复制代码function App() {
    const count = useCountStore(state => state.count)
    const inc = useCountStore(state => state.inc)
    
    return (
    <>
      <button onClick={() => inc()}>count: {count}</button>
    </>
    )
    }</code></pre>
    <p>查看效果:</p>
    <p><img src="https://p3-juejin.byteimg.com/tos-cn-i-k3u1fbpfcp/6ff69c6470f347eb8dafa3f8e58c3309~tplv-k3u1fbpfcp-jj-mark:3024:0:0:0:q75.awebp#?w=530&amp;h=242&amp;s=32691&amp;e=gif&amp;f=19&amp;b=f9f9f9" alt="" /></p>
    <p>计数按照预期的增加了。</p>
    <h2>更新嵌套状态</h2>
    <p>值得注意的是,<strong>Zustand 的局部更新只适应于第一层属性,对嵌套对象中的属性是无效的</strong>。</p>
    <p>我们先看个反例:</p>
    <p>```jsx
    jsx 体验AI代码助手 代码解读复制代码const useCountStore = create((set) => ({
    nested: { other: &#039;other&#039;, count: 0 },
    inc: () =>
    // × 这么做是错的
    set((state) => ({
    nested: { count: state.nested.count + 1 },
    })),
    }))</p>
    <pre><code>
    更新 App 组件,再看看效果。
    
    ```jsx
    jsx 体验AI代码助手 代码解读复制代码function App() {
    const nested = useCountStore(state => state.nested)
    const inc = useCountStore(state => state.inc)
    
    return (
    <>
      

    {JSON.stringify(nested)}

    ) }

    效果:

    发现嵌套对象中的 other 属性不见了。

    正确的做法应该是这样:

    ```diff
    diff 体验AI代码助手 代码解读复制代码set((state) => ({

    • nested: { count: state.nested.count + 1 },
    • nested: { ...state.nested, count: state.nested.count + 1 },
      }))

    再来看看效果:

    这样就没有问题了。

    不过,这样更新嵌套对象的方式,着实有些麻烦,如果嵌套层级过深,写出来的代码就极其的丑陋。

    jsx 体验AI代码助手 代码解读复制代码// × 不要这么做!
    normalInc: () =>
    set((state) => ({
    deep: {
      ...state.deep,
      nested: {
        ...state.deep.nested,
        obj: {
          ...state.deep.nested.obj,
          count: state.deep.nested.obj.count + 1
        }
      }
    }
    })),

    这个 Zustand 作者也帮我们想到了,提供了 Immer middleware 帮我们做这件事。

    Immer middleware 直接依赖 immer,因此我们还需要安装 immer。

    ```bash
    bash 体验AI代码助手 代码解读复制代码npm install immer

    
    接着,项目中引入:
    
    ```diff
    diff 体验AI代码助手 代码解读复制代码import { create } from 'zustand'
    + import { immer } from 'zustand/middleware/immer'

    此 immer 非彼 immer,middleware/immer 是在 immer 之上的一层封装,为了更好地跟 Zustand 在一起协作。

    还是以上方的 useCountStore() 为例。

    ```jsx
    jsx 体验AI代码助手 代码解读复制代码const useCountStore = create((set) => ({
    nested: { other: 'other', count: 0 },
    inc: () =>
    set((state) => ({
    nested: { ...state.nested, count: state.nested.count + 1 },
    })),
    }))

    
    
    我们只做 2 件事。
  3. create() 回调函数采用 immer 包裹

    1. set() 回调函数内部没有返回值,直接针对 state 进行修改
    jsx 体验AI代码助手 代码解读复制代码const useCountStore = create(/* 1 */immer((set) => ({
    nested: { other: 'other', count: 0 },
    inc: () =>
    set((state) => {
      state.nested.count++ /* 2 */
    }),
    })))

    查看效果:

    跟以前一样。

    一旦接入 immer,那么状态更新可以统一改成直接修改 state 的方式,这样更加一致和易于维护。

  • 0人 Love
  • 0人 Haha
  • 0人 Wow
  • 0人 Sad
  • 0人 Angry
React

作者简介: Jiafeng

共 0 条评论关于 “React — zustand状态管理工具”

Loading...