状态管理
从数据可变性上,主流两种分类
- 数据可变(mutable)
- mobx
- 一些利用 proxy API 的库
- 数据不可变(immutable)
- redux
- redux 系列变体
- zustand(
set做浅合并更新,写法很轻)
vue中有 数据不可变(immutable)状态管理吗?
答案: 几乎没有;
immutable 适用于函数式编程,vue可以做到函数式,但显然不是最佳实践。
当前主流mutable数据管理 使用技术方案
个人角度 不在乎状态管理 是不是 数据不可变
更加简单 高效的 搞定状态管理
核心诉求:
- 更少的api
- 更少的心智负担
vue: pinia(vuex5.x 版本) 越来越 少的api 和 心智负担
react: zustand / mobx 越来越 少的api 和 心智负担
rxjs只是异步数据流的工具函数,不依赖rxjs 依然可以实现分层治理
世界主流 的状态管理 大多数情况 还是 数据可变的。 数据不可变的状态管理,更加适用于函数式编程。 对于函数式编程,在整体上 并不是主流。 大多数情况是 面向对象+面向过程的组合,很难函数式到底,基本都不够纯粹。
zustand
对应上面的诉求:几乎没有 Provider / boilerplate,store 就是一个 hook。
安装
npm i zustand
创建 store
import { create } from 'zustand'
interface CounterState {
count: number
increase: () => void
decrease: () => void
reset: () => void
}
export const useCounterStore = create<CounterState>()((set) => ({
count: 0,
increase: () => set((state) => ({ count: state.count + 1 })),
decrease: () => set((state) => ({ count: state.count - 1 })),
reset: () => set({ count: 0 }),
}))
要点:
create返回的是 hookset会浅合并新状态;也可传入函数set((state) => nextPartial)- action 直接写在 store 里,组件只消费
组件里用(选择器)
function Counter() {
// 只订阅 count:count 变才重渲染
const count = useCounterStore((s) => s.count)
const increase = useCounterStore((s) => s.increase)
return (
<button type="button" onClick={increase}>
count: {count}
</button>
)
}
不要这样写(整 store 一变就重渲染):
const store = useCounterStore()
常用写法:get、异步 action
import { create } from 'zustand'
interface UserState {
user: { id: string; name: string } | null
loading: boolean
fetchUser: (id: string) => Promise<void>
logout: () => void
}
export const useUserStore = create<UserState>()((set, get) => ({
user: null,
loading: false,
fetchUser: async (id) => {
set({ loading: true })
const res = await fetch(`/api/users/${id}`)
const user = await res.json()
// get() 读当前快照,不必依赖闭包里的旧值
console.log('before:', get().user)
set({ user, loading: false })
},
logout: () => set({ user: null }),
}))
任意组件都能调同一份 store,天然跨组件共享,不需要 Context 包一层。
function Profile() {
const user = useUserStore((s) => s.user)
const fetchUser = useUserStore((s) => s.fetchUser)
return (
<button type="button" onClick={() => fetchUser('1')}>
{user?.name ?? '加载用户'}
</button>
)
}
一次取多个字段:useShallow
选择器如果返回 新对象 / 新数组,默认每次引用都不同,容易多余渲染。用 useShallow 做浅比较:
import { useShallow } from 'zustand/react/shallow'
function Panel() {
const { count, increase } = useCounterStore(
useShallow((s) => ({ count: s.count, increase: s.increase })),
)
// 或数组形式
// const [count, increase] = useCounterStore(
// useShallow((s) => [s.count, s.increase] as const),
// )
return (
<button type="button" onClick={increase}>
{count}
</button>
)
}
可选:persist(本地持久化)
需要记住登录态、主题等时,套一层 middleware 即可:
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
interface ThemeState {
theme: 'light' | 'dark'
toggle: () => void
}
export const useThemeStore = create<ThemeState>()(
persist(
(set, get) => ({
theme: 'light',
toggle: () =>
set({ theme: get().theme === 'light' ? 'dark' : 'light' }),
}),
{
name: 'theme-storage', // localStorage key,需唯一
// storage: createJSONStorage(() => sessionStorage), // 默认 localStorage
// partialize: (s) => ({ theme: s.theme }), // 只持久化部分字段
},
),
)
小结:日常业务状态优先 zustand —— API 少、无 Provider、选择器控制渲染面;需要响应式可变模型再看 mobx。