zustand
最后更新于
这有帮助吗?
最后更新于
这有帮助吗?
zustand
是一个使用简化的 flux
原理的小型、快速和可扩展的 bearbones
状态管理解决方案。基于hooks
有一个的 API。
你的 store
是一个 hook
!你可以在里面放任何内容:基本类型、对象、函数。状态必须不可变地更新,并且 set
函数来帮助它。
import { create } from "zustand";
const useCount = create((set) => ({
count: 0,
plus: () => set((state) => ({count: state.count + 1})),
minus: () => set((state) => ({count: state.count - 1})),
reset: () => set({count: 0}),
}));
然后绑定你的组件,在任何地方使用 hook
,不需要 provider
。选择你的状态,组件将在 state
更改时重新渲染。
function Counter() {
const count = useCount((state) => state.count);
return <h1>{count}</h1>;
}
function Controls() {
const plus = useCount((state) => state.plus);
const minus = useCount((state) => state.minus);
const reset = useCount((state) => state.reset);
return (
<>
<button onClick={plus}>+</button>
<button onClick={minus}>-</button>
<button onClick={reset}>重置</button>
</>
);
}
注意
const state = useCount(); // 它会导致组件在每次 state 更改时候重新渲染
切片状态(slice state),因为 store 是一个原子状态,它可以切分为多个格子状态,便于代码管理。
默认情况下,它以严格相等(old === new)检测更改,这对于原子状态选择非常有效。
const nuts = useStore((state) => state.nuts);
const honey = useStore((state) => state.honey);
import {create} from "zustand";
import {useShallow} from "zustand/react/shallow";
const useBearStore = create((set) => ({
nuts: 0,
honey: 0,
treats: {},
// ...
}));
// Object pick, re-renders the component when either state.nuts or state.honey change
const {nuts, honey} = useBearStore(useShallow((state) => ({nuts: state.nuts, honey: state.honey})));
// Array pick, re-renders the component when either state.nuts or state.honey change
const [nuts, honey] = useBearStore(useShallow((state) => [state.nuts, state.honey]));
// Mapped picks, re-renders the component when state.treats changes in order, count or keys
const treats = useBearStore(useShallow((state) => Object.keys(state.treats)));
为了更好地控制渲染,可以提供任何自定的函数。
const treats = useBearStore(
(state) => state.treats,
(oldTreats, newTreats) => compare(oldTreats, newTreats)
);
set 函数有第二个参数,默认为 false,它将取代 state 模型,而不是合并。注意它会抹去你依赖的部分,比如 actions。
import omit from "lodash-es/omit";
const useStore = create((set) => ({
salmon: 1,
tuna: 2,
deleteEverything: () => set({}, true), // 清除整个存储,包括操作
deleteTuna: () => set((state) => omit(state, ["tuna"]), true),
}));
const useStore = create((set) => ({
girls: {},
fetch: async (pond) => {
const response = await fetch(pond);
set({girls: await response.json()});
},
}));
const useStore = create((set, get) => ({
name: "Lucy",
action: () => {
const name = get().name
// ...
}
})
通常建议使用 useCallback 记忆处理器。 这将防止每次渲染时进行不必要的计算。 它还允许 React 在并发模式下优化性能。
const fruit = useStore(useCallback((state) => state.fruits[id], [id]));
如果一个处理器不依赖于作用域,可以在渲染函数之外定义它以获得一个固定的“引用”而无需 useCallback。
const selector = (state) => state.berries;
function Component() {
const berries = useStore(selector);
}
const useStore = create(() => ({paw: true, snout: true, fur: true}));
// 在React组件之外获取state
const paw = useStore.getState().paw; //true
// 侦听所有更改,在每次更改时同步触发
const unsub = useStore.subscribe(console.log);
// 更新状态,将触发侦听器
useStore.setState({paw: false});
// 取消订阅侦听
unsub();
// 销毁商店(删除所有侦听器)
useStore.destroy();
// 当然可以像hook一样使用
function Component() {
const paw = useStore((state) => state.paw);
}
如果您需要使用处理器订阅,subscribeWithSelector
中间件会有所帮助。
有了这个中间件,subscribe 接受一个额外的签名:
subscribe(selector, callback, options ? : {equalityFn, fireImmediately})
:
Unsubscribe
import {subscribeWithSelector} from "zustand/middleware";
const useStore = create(subscribeWithSelector(() => ({paw: true, snout: true, fur: true})));
// 订阅状态值变化
const unsub2 = useStore.subscribe((state) => state.paw, console.log);
// 订阅上一个状态值
const unsub3 = useStore.subscribe(
(state) => state.paw,
(paw, previousPaw) => console.log(paw, previousPaw)
);
// 订阅一个可选的浅差异方法
const unsub4 = useStore.subscribe((state) => [state.paw, state.fur], console.log, {equalityFn: shallow});
// 解除订阅
const unsub5 = useStore.subscribe((state) => state.paw, console.log, {fireImmediately: true});
import create, {GetState, SetState} from "zustand";
import {StoreApiWithSubscribeWithSelector, subscribeWithSelector} from "zustand/middleware";
type BearState = {
paw: boolean;
snout: boolean;
fur: boolean;
};
const useStore = create<
BearState,
SetState<BearState>,
GetState<BearState>,
StoreApiWithSubscribeWithSelector<BearState>
>(subscribeWithSelector(() => ({paw: true, snout: true, fur: true})));
订阅功能允许组件绑定到 state,而无需在更改时强制重新渲染。 最好将它与 useEffect
结合使用,以便在卸载时自动取消订阅。 当直接改变视图时,这会对性能产生巨大影响。
const useStore = create(set => ({girlNum: 0, ...}))
function Component() {
// 获取初始状态
const girlNumRef = useRef(useStore.getState().girlNum)
// 在挂载时连接到Store,在卸载时断开连接,在引用时捕获状态变化
useEffect(() => useStore.subscribe(
state => (girlNumRef.current = state.girlNum)
), [])
}
import produce from "immer";
const useStore = create((set) => ({
lush: {forest: {contains: {a: "bear"}}},
clearForest: () =>
set(
produce((state) => {
state.lush.forest.contains = null;
})
),
}));
const clearForest = useStore((state) => state.clearForest);
clearForest();
// 记录每次改变的state
const log = (config) => (set, get, api) =>
config(
(args) => {
console.log(" applying", args);
set(args);
console.log(" new state", get());
},
get,
api
);
// 将 set 方法变成一个 immer proxy
const immer = (config) => (set, get, api) =>
config(
(partial, replace) => {
const nextState = typeof partial === "function" ? produce(partial) : partial;
return set(nextState, replace);
},
get,
api
);
const useStore = create(
log(
immer((set) => ({
bees: false,
setBees: (input) => set((state) => void (state.bees = input)),
}))
)
);
import create from "zustand";
import produce from "immer";
import pipe from "ramda/es/pipe";
/* 上一个例子中的日志和immer函数 */
/* 通过pipe集合任意数量的中间件 */
const createStore = pipe(log, immer, create);
const useStore = createStore((set) => ({
bears: 1,
increasePopulation: () => set((state) => ({bears: state.bears + 1})),
}));
export default useStore;
import create from "zustand";
import {devtools, redux} from "zustand/middleware";
import pipe from "ramda/es/pipe";
const log: typeof devtools = (config) => (set, get, api) =>
config(
(args) => {
console.log(" applying", args);
set(args);
console.log(" new state", get());
},
get,
api
);
export type State = {
grumpiness: number;
};
const initialState: State = {
grumpiness: 0,
};
const createStore = pipe(redux, devtools, log, create);
enum types {
increase = "INCREASE",
decrease = "DECREASE",
}
const reducer = (state: State, {type, by = 1}: { type: types; by: number }) => {
switch (type) {
case types.increase:
return {grumpiness: state.grumpiness + by};
case types.decrease:
return {grumpiness: state.grumpiness - by};
}
};
const useStore = createStore(reducer, initialState);
export default useStore;
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 useStore = create((set) => ({
grumpiness: 0,
dispatch: (args) => set((state) => reducer(state, args)),
}));
const dispatch = useStore((state) => state.dispatch);
dispatch({type: types.increase, by: 2});
或者,使用 redux-middleware
import React, {useEffect} from "react";
import ReactDOM from "react-dom";
import create from "zustand";
import {devtools, redux} from "zustand/middleware";
import "./styles.css";
const initialState = {count: 0};
const types = {increase: "INCREASE", decrease: "DECREASE"};
const reducer = (state, {type, by}) => {
switch (type) {
case types.increase:
return {count: state.count + by};
case types.decrease:
return {count: state.count - by};
default:
return;
}
};
const [useStore, api] = create(
// 将 store 连接到 devtools
// 如果没有 reducers 和 action-types,你会看到“setState”被注销
devtools(
// 将我们的 store 转换为 redux action dispatcher ...
// 向 store 添加一个 dispatch 方法
redux(reducer, initialState)
)
);
function Counter() {
const count = useStore((state) => state.count);
useEffect(() => {
// Increase
setTimeout(() => api.dispatch({type: types.increase, by: 3}), 1000);
// Decrease
setTimeout(() => api.dispatch({type: types.decrease, by: 1}), 2000);
// Decrease
setTimeout(() => api.dispatch({type: types.decrease, by: 1}), 3000);
}, []);
return <span class="header">{count}</span>;
}
ReactDOM.render(<Counter/>, document.getElementById("root"));
如果在 React 事件处理程序之外调用setState
,它会同步处理。 在事件处理程序之外更新状态将强制 react 同步更新组件,因此增加了遇到僵尸子效应的风险。 为了解决这个问题,需要将 actions 包裹在unstable_batchedUpdates
中。
import {unstable_batchedUpdates} from "react-dom"; // or 'react-native'
const useStore = create((set) => ({
fishes: 0,
increaseFishes: () => set((prev) => ({fishes: prev.fishes + 1})),
}));
const nonReactCallback = () => {
unstable_batchedUpdates(() => {
useStore.getState().increaseFishes();
});
};
import {devtools} from "zustand/middleware";
// 使用普通操作存储,它将记录操作为“setState”
// devtools 将只记录来自每个单独存储的操作,这与典型的 redux 存储不同
const useStore = create(devtools(store));
// 使用 redux 存储,它将记录完整操作类型
const useStore = create(devtools(redux(reducer, initialState)));
Name store: devtools(store, {name: "MyStore"})
,这将在 devtools 中创建一个名为“MyStore”的单独实例。 序列化选项:devtools(store, { serialize: { options: true } })
。
store create
不需要上下文提供程序(context providers)。 在某些情况下,你可能希望使用上下文进行依赖注入,或者如果你想使用组件中的 props 初始化 store。 因为 store 是一个钩子,把它作为一个普通的上下文值传递可能会违反钩子的规则。 为了避免误用,提供了一个特殊createContext
。
import create from 'zustand'
import createContext from 'zustand/context'
const {Provider, useStore} = createContext()
const createStore = () => create(...)
const App = () => (
<Provider createStore={createStore}>
...
</Provider>
)
const Component = () => {
const state = useStore()
const slice = useStore(selector)
...
}
在组件中使用
import create from "zustand";
import createContext from "zustand/context";
// 最佳实践:可以将下面的 createContext() 和 createStore 移动到单独的文件 (store.js) 并导入提供程序Provider,在此处/任何需要的地方使用 store。
const {Provider, useStore} = createContext();
const createStore = () =>
create((set) => ({
bears: 0,
increasePopulation: () => set((state) => ({bears: state.bears + 1})),
removeAllBears: () => set({bears: 0})
}));
const Button = () => {
return (
{/** store() - 每次使用 Button 组件都创建一个 store,而不是为所有组件使用一个 store **/}
<Provider createStore={createStore}>
<ButtonChild/>
</Provider>
)
;
};
const ButtonChild = () => {
const state = useStore();
return (
<div>
{state.bears}
<button
onClick={() => {
state.increasePopulation();
}}
>
+
</button>
</div>
);
};
export default function App() {
return (
<div className="App">
<Button/>
<Button/>
</div>
);
}
import create from "zustand";
import createContext from "zustand/context";
type
BearState = {
bears: number
increase: () => void
}
// 将类型传递给 `createContext` 而不是 `create`
const {Provider, useStore} = createContext < BearState > ();
export default function App({initialBears}: { initialBears: number }) {
return (
<Provider
createStore={() =>
create((set) => ({
bears: initialBears,
increase: () => set((state) => ({bears: state.bears + 1})),
}))
}
>
<Button/>
</Provider>
)
// 可以使用 `type`
type BearState = {
bears: number;
increase: (by: number) => void;
};
// 或者 `interface`
interface BearState {
bears: number;
increase: (by: number) => void;
}
// 它对两者都有效
const useStore = create<BearState>((set) => ({
bears: 0,
increase: (by) => set((state) => ({bears: state.bears + by})),
}));
combine
并让 tsc 推断类型import {combine} from "zustand/middleware";
const useStore = create(
combine({bears: 0}, (set) => ({increase: (by: number) => set((state) => ({bears: state.bears + by}))}))
);
如果想要构造一个内部有多个 state-pick 的单个对象,类似于 redux 的 mapStateToProps,你可以使用 来防止当 selector 输出没有根据 shallow 相等而改变时不必要的重新渲染。