前言
学习 React 的过程中,我发现问题往往不是 API 本身,而是状态该放哪里与谁来维护。把状态留在组件内部能快速得到反馈,但当多个组件需要共享数据时,props 的层层传递会迅速拉散逻辑,代码的关注点也随之模糊。
为了解决这个问题,我们可以把状态抽离到一个统一的位置:
把它当成一个仓库,数据在这里存放,修改数据的方法也在这里,组件按需从仓库取用而非各自保管。
本文围绕 Zustand 展开,展示如何用它搭建这样一个状态仓库并让组件围绕这个仓库协作。若还没安装Zustand可执行 npm install zustand。
详情可以点👇去官网看看:
搭建最小可用的状态仓库
我们先做最小可用的 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 让状态的来源可见,也让日常开发少一些绕弯路。
于我而言,先把事情做清楚,比过早引入复杂方案要更有用。如果你也在学习过程中被状态问题反复困扰,不妨亲手试一次这种写法,看看是否更顺手。