「zustand」更够快速构建你的项目状态管理方案
zustand
- Zustand 是由一个名为 pmndrs 的团队开发和维护的状态管理库。
- pmndrs 是一个由多位开发者组成的团队,专注于构建开源工具和库来推动 Web 开发的进步。
zustand优势
-
简单易用: Zustand 的 API 设计简单直观,学习曲线较低。与其他一些状态管理库相比,Zustand 提供了更少的概念和 API,减少了开发者的认知负担。
-
基于钩子: Zustand 使用 React 的钩子机制作为状态管理的基础。它通过创建自定义 Hook 来提供对状态的访问和更新。这种方式与函数式组件和钩子的编程模型紧密配合,使得状态管理变得非常自然和无缝。
-
可拓展性: Zustand 提供了中间件 (middleware) 的概念,允许你通过插件的方式扩展其功能。中间件可以用于处理日志记录、持久化存储、异步操作等需求,使得状态管理更加灵活和可扩展。
-
性能优化: Zustand 在设计时非常注重性能。它采用了高效的状态更新机制,避免了不必要的渲染。同时,Zustand 还支持分片状态和惰性初始化,以提高大型应用程序的性能。
-
无副作用: Zustand 鼓励无副作用的状态更新方式。它倡导使用 immer 库来处理不可变性,使得状态更新更具可预测性,也更易于调试和维护。
图为:新一代状态管理方案,目前已经有 34k Star.
快速上手 —— 3 步实现基础状态管理
安装
npm install zustand //
yarn add zustand
// 计数器 Demo 快速上手
import React from "react";
import { create } from "zustand";
const useStore = create()((set) => ({
count: 0,
setCount: (num: number) => set({ count: num }),
inc: () => set((state) => ({ count: state.count + 1 })),
}));
export default function Demo() {
// 在这里引入所需状态
const { count, setCount, inc } = useStore();
return (
<div>
{count}
<input
onChange={(event) => {
setCount(Number(event.target.value));
}}
></input>
<button onClick={inc}>增加</button>
</div>
);
}
快速上手 —— 分解步骤
- 导入 create 函数
import { create } from 'zustand';
- 创建 Store
const useStore = create()((set) => ({
count: 0,
setCount: (num: number) => set({ count: num }),
inc: () => set((state) => ({ count: state.count + 1 })),
}));
- 使用State
export default function Demo() {
// 在这里引入所需状态
const { count, setCount, inc } = useStore();
return (
<div>
{count}
<input
onChange={(event) => {
setCount(Number(event.target.value));
}}
></input>
<button onClick={inc}>增加</button>
</div>
);
}
zustand 与 redux 进行对比
react-redux 版本
import { createStore } from 'redux'
import { useSelector, useDispatch } from 'react-redux'
type State = {
count: number
}
type Action = {
type: 'increment' | 'decrement'
qty: number
}
const countReducer = (state: State, action: Action) => {
switch (action.type) {
case 'increment':
return { count: state.count + action.qty }
case 'decrement':
return { count: state.count - action.qty }
default:
return state
}
}
const countStore = createStore(countReducer)
const Component = () => {
const count = useSelector((state) => state.count)
const dispatch = useDispatch()
// ...
}
toolkit版本
import { useSelector } from 'react-redux'
import type { TypedUseSelectorHook } from 'react-redux'
import { createSlice, configureStore } from '@reduxjs/toolkit'
const countSlice = createSlice({
name: 'count',
initialState: { value: 0 },
reducers: {
incremented: (state, qty: number) => {
// Redux Toolkit does not mutate the state, it uses the Immer library
// behind scenes, allowing us to have something called "draft state".
state.value += qty
},
decremented: (state, qty: number) => {
state.value -= qty
},
},
})
const countStore = configureStore({ reducer: countSlice.reducer })
const useAppSelector: TypedUseSelectorHook<typeof countStore.getState> =
useSelector
const useAppDispatch: () => typeof countStore.dispatch = useDispatch
const Component = () => {
const count = useAppSelector((state) => state.count.value)
const dispatch = useAppDispatch()
// ...
}
zustand 版本
import { create } from 'zustand'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
const useCountStore = create<State & Actions>((set) => ({
count: 0,
increment: (qty: number) => set((state) => ({ count: state.count + qty })),
decrement: (qty: number) => set((state) => ({ count: state.count - qty })),
}))
const Component = () => {
const { count , increment , decrement} = useCountStore();
// ...
}
对比下来,zustand简直不要太简单
reset state 重置状态
单仓库重置
直接传入reset函数, 使用时调用reset函数
import { create } from 'zustand'
// define types for state values and actions separately
type State = {
salmon: number
tuna: number
}
type Actions = {
addSalmon: (qty: number) => void
addTuna: (qty: number) => void
reset: () => void
}
// define the initial state
const initialState: State = {
salmon: 0,
tuna: 0,
}
// create store
const useSlice = create<State & Actions>()((set, get) => ({
...initialState,
addSalmon: (qty: number) => {
set({ salmon: get().salmon + qty })
},
addTuna: (qty: number) => {
set({ tuna: get().tuna + qty })
},
reset: () => {
set(initialState)
},
}))
多仓库重置
import { create as _create, StateCreator } from 'zustand'
const resetters: (() => void)[] = []
export const create = (<T extends unknown>(f: StateCreator<T> | undefined) => {
if (f === undefined) return create
const store = _create(f)
const initialState = store.getState()
resetters.push(() => {
store.setState(initialState, true)
})
return store
}) as typeof _create
export const resetAllStores = () => {
for (const resetter of resetters) {
resetter()
}
}
async operation 异步操作
zustand 不会单独区分异步操作,可直接进行异步即可
const useStore = create((set) => ({
obj: {},
fetch: async (req) => {
const response = await fetch(req)
set({ obj: await response.json() })
},
}))
middleware中间件
- 持久化存储
import { create } from 'zustand'
import { persist, createJSONStorage } from 'zustand/middleware'
export const useBearStore = create(
persist(
(set, get) => ({
bears: 0,
addABear: () => set({ bears: get().bears + 1 }),
}),
{
name: 'food-storage', // name of the item in the storage (must be unique)
storage: createJSONStorage(() => sessionStorage), // (optional) by default, 'localStorage' is used
}
)
)
- 监控日志
// state 每次发生变化都将输出日志
const log = (config) => (set, get, api) =>
config(
(...args) => {
console.log(' applying', args)
set(...args)
console.log(' new state', get())
},
get,
api
)
- immer不可变数据实现
import { create } from 'zustand'
import { immer } from 'zustand/middleware/immer'
type State = {
count: number
}
type Actions = {
increment: (qty: number) => void
decrement: (qty: number) => void
}
export const useCountStore = create(
immer<State & Actions>((set) => ({
count: 0,
increment: (qty: number) =>
set((state) => {
state.count += qty
}),
decrement: (qty: number) =>
set((state) => {
state.count -= qty
}),
}))
)
- Devtools middle 开发者工具调试state
import { devtools, persist } from 'zustand/middleware'
const useFishStore = create(
devtools(persist(
(set, get) => ({
fishes: 0,
addAFish: () => set({ fishes: get().fishes + 1 }),
}),
))
)
- 如果你仍然想写redux
import { redux } from 'zustand/middleware'
const types = { increase: 'INCREASE', decrease: 'DECREASE' }
const reducer = (state, { type, by = 1 }) => {
switch (type) {
case types.increase:
return { grumpiness: state.grumpiness + by }
case types.decrease:
return { grumpiness: state.grumpiness - by }
}
}
const initialState = {
grumpiness: 0,
dispatch: (args) => set((state) => reducer(state, args)),
}
const useReduxStore = create(redux(reducer, initialState))
笔者推荐的 zustand 代码格式
import React from "react";
import { create } from "zustand";
// define types for state values and actions separately
type States = {
salmon: number;
tuna: number;
};
type Actions = {
addSalmon: (qty: number) => void;
addTuna: (qty: number) => void;
reset: () => void;
};
// define the initial state
const initialState: States = {
salmon: 0,
tuna: 0
};
// create store
const useSlice = create<States & Actions>()((set, get) => ({
...initialState,
addSalmon: (qty: number) => {
set({ salmon: get().salmon + qty });
},
addTuna: (qty: number) => {
set({ tuna: get().tuna + qty });
},
reset: () => {
set(initialState);
}
}));
export default function App() {
// const {salmon,addSalmon,tuna,addTuna,reset} = useSlice();
// value
const { salmon, tuna } = useSlice();
// aciton
const { addSalmon, addTuna, reset } = useSlice();
return (
<div className="App">
{salmon}{" "}
<button type="button" onClick={() => addSalmon(1)}>
Add Salmon
</button>
<hr />
{tuna}{" "}
<button type="button" onClick={() => addTuna(1)}>
Add Tuna
</button>
<hr />
<button type="button" onClick={() => reset()}>
Reset
</button>
</div>
);
}