把状态放进“仓库”:用 Zustand 让 React 代码更轻

62 阅读4分钟

前言

学习 React 的过程中,我发现问题往往不是 API 本身,而是状态该放哪里与谁来维护。把状态留在组件内部能快速得到反馈,但当多个组件需要共享数据时,props 的层层传递会迅速拉散逻辑,代码的关注点也随之模糊。

为了解决这个问题,我们可以把状态抽离到一个统一的位置:
把它当成一个仓库,数据在这里存放,修改数据的方法也在这里,组件按需从仓库取用而非各自保管。

本文围绕 Zustand 展开,展示如何用它搭建这样一个状态仓库并让组件围绕这个仓库协作。若还没安装Zustand可执行 npm install zustand
详情可以点👇去官网看看:

zustand-npm

搭建最小可用的状态仓库

我们先做最小可用的 store:能存数据、能改数据。

// src/store/count.js
import { create } from 'zustand'

const useCountStore = create((set) => ({
  count: 0,
  age: 19,
  // 修改方法 —— 使用 set 更新状态
  increase: () => {
    // set 接受一个函数,返回要合并的新状态
    set((state) => ({ count: state.count + 1 }))
  },

  decrease: (val) => {
    set((state) => ({ count: state.count - val }))
  }
}))

export default useCountStore

这段代码看起来并不起眼,但它背后有一个很关键的认知转变:Zustand 创建出来的 store,本质上就是一个 Hook。useCountStore 不是配置文件,也不是某种“注册行为”,而是一个可以被组件直接调用的函数入口。

你可以把它理解成一个“自带状态的 Hook”,数据和修改数据的方式被自然地放在了一起,而组件只是使用者。

组件如何订阅与使用状态

组件通过调用 store 来订阅需要的字段,心智负担明显下降。下面两个最小示例分别展示修改和只读场景。

Home:负责“改状态”

import useCountStore from '../store/count'

export default function Home() {
  // selector 只订阅需要的字段/方法,避免不必要的重渲染
  const count = useCountStore(state => state.count)
  const increase = useCountStore(state => state.increase)
  const decrease = useCountStore(state => state.decrease)

  return (
    <div>
      {/* 直接调用仓库里的方法,无需派发 action 或穿透 props */}
      <button onClick={increase}>增加 - {count}</button>
      <button onClick={() => decrease(10)}>减少 - {count}</button>
    </div>
  )
}

可以把这种写法理解成调电台:组件只声明自己“想听哪个频道”,至于其他状态是否变化,与它无关。count 发生变化时组件会更新,而 age 即使改变,这个组件也完全不会受到影响。

当另一个组件只负责展示同一份状态时,这种关系就更加直观了。

About:只读,不改

import useCountStore from '../store/count'

export default function About() {
  // 这里仅订阅 count,用于展示
  const count = useCountStore(state => state.count)

  return <h2>当前计数:{count}</h2>
}

Home 负责修改状态,About 只负责读取状态,两者之间没有任何直接通信代码,却能保持完全同步。这种“松耦合”的状态流向,在学习阶段对理解状态的真正来源非常友好。

把请求也放进仓库

真实项目里,状态往往并不是写死在代码里的,而是来自接口请求:请求也可以放在仓库里,组件只负责何时调用。

仓库里处理接口逻辑:

// src/store/list.js
import { create } from 'zustand'
// 把接口请求封装成函数,方便测试和复用
const fetchApi = async () => {
  const res = await fetch('https://mock.mengxuegu.com/mock/xxx')
  const data = await res.json()
  return data.data
}

const useListStore = create((set) => ({
  list: [],
  // 在 store 内部处理请求并更新状态
  fetchList: async () => {
    const result = await fetchApi()
    // 直接用 set 更新 store 中的 list
    set({ list: result })
  }
}))

export default useListStore

组件只关心“用”:

import { useEffect } from 'react'
import useListStore from '../store/list'

export default function List() {
  // 订阅列表数据与触发请求的方法
  const list = useListStore(state => state.list)
  const fetchList = useListStore(state => state.fetchList)

  useEffect(() => {
    // 组件决定“什么时候要数据”,调用仓库方法获取并存储
    fetchList()
    // 依赖空数组表示组件挂载时仅调用一次
  }, [])

  return (
    <div>
      {list.map(item => (
         // 保持 key 的稳定性,避免用索引
        <div key={item.name}>{item.name}</div>
      ))}
    </div>
  )
}

Zustand 与其说是在“管理状态”,不如说是在帮我把业务逻辑集中收纳起来。


写在最后

我把这套做法当成给代码减负的一个小技巧:把数据和修改方法放到一个明确的“仓库”里,组件只负责用,职责清晰了,问题也更容易找到。Zustand 让状态的来源可见,也让日常开发少一些绕弯路。

于我而言,先把事情做清楚,比过早引入复杂方案要更有用。如果你也在学习过程中被状态问题反复困扰,不妨亲手试一次这种写法,看看是否更顺手。