前端
PythonJava运维数据库
状态管理
状态管理
  • 🖥️Redux Tookit
    • Introduction
      • 介绍
    • Tutorials
      • 🖥️快速开始
      • 🏐在TypeScript环境使用
    • Using Redux toolkit
      • 使用Immer编写Reducers
      • 性能与数据范式化
      • 异步逻辑与数据请求
      • Usage With TypeScript
    • API Refrence
      • Store Setup
        • configureStore
        • getDefaultMiddleware
        • Immutability Middleware
        • Serializability Middleware
        • Action Creator Middleware
        • createListenerMiddleware
        • createDynamicMiddleware
        • getDefaultEnhancers
        • autoBatchEnhancer
      • Reducer and Actions
        • createReducer
        • createAction
        • createSlice
        • createAsyncThunk
        • createEntityAdapter
        • combineSlices
      • Other
        • createSelector
        • Matching Utilities
    • RTX Query
      • RTK Query Overview
      • Comparson with Other Tools
      • Examples
      • Usage With TypeScript
      • Using RTK Query
        • Queries
        • Mutations
      • API Refrence
        • createApi
        • fetchBaseQuery
        • ApiProvider
        • setupListeners
        • Generated API Slices
          • Generated API Slices
          • Redux Integration
          • Endpoints
          • Code Splitting
          • Utilities
          • React Hooks
  • 🚚Redux-Saga
    • 入门
    • 基础概念
    • API
    • 技巧
  • 🚑React Redux
    • 安装
    • 教程
    • 使用 React Redux
    • API 参考
  • 🚕生态
    • Redux Persist
    • zustand
      • 使用 useShallow 防止重新渲染
由 GitBook 提供支持
在本页
  • 简单使用
  • state 更新渲染组件
  • 单个 state 更新渲染
  • 多个 state 更新渲染
  • 自定义函数控制渲染
  • 覆盖 state
  • 异步操作
  • 从 action 中读取 state
  • 记忆处理器
  • useCallback
  • 不依赖于作用域的处理器
  • 在 React 组件之外读写 state
  • 自定义 hooks 读写 state
  • 使用订阅处理器(中间件)
  • 瞬时更新 ref(用于频繁发生的状态变化)
  • 更新嵌套的状态,使用
  • Immer
  • 中间件
  • 按自己喜欢的方式管理 store
  • 管理中间件
  • 状态持久化中间件 persist
  • 像 Redux 一样编写代码
  • 在 React 事件处理程序之外调用 actions
  • 使用 Redux 开发工具
  • React context
  • TypeScript 类型定义

这有帮助吗?

  1. 生态

zustand

上一页Redux Persist下一页使用 useShallow 防止重新渲染

最后更新于6个月前

这有帮助吗?

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 更改时候重新渲染

state 更新渲染组件

切片状态(slice state),因为 store 是一个原子状态,它可以切分为多个格子状态,便于代码管理。

单个 state 更新渲染

默认情况下,它以严格相等(old === new)检测更改,这对于原子状态选择非常有效。

const nuts = useStore((state) => state.nuts);
const honey = useStore((state) => state.honey);

多个 state 更新渲染

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)
);

覆盖 state

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()});
    },
}));

从 action 中读取 state

const useStore = create((set, get) => ({
    name: "Lucy",
    action: () => {
        const name = get().name
        // ...
    }
})

记忆处理器

useCallback

通常建议使用 useCallback 记忆处理器。 这将防止每次渲染时进行不必要的计算。 它还允许 React 在并发模式下优化性能。

const fruit = useStore(useCallback((state) => state.fruits[id], [id]));

不依赖于作用域的处理器

如果一个处理器不依赖于作用域,可以在渲染函数之外定义它以获得一个固定的“引用”而无需 useCallback。

const selector = (state) => state.berries;

function Component() {
    const berries = useStore(selector);
}

在 React 组件之外读写 state

自定义 hooks 读写 state

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});

订阅处理器(中间件)使用 TS

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})));

瞬时更新 ref(用于频繁发生的状态变化)

订阅功能允许组件绑定到 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)
    ), [])
}

更新嵌套的状态,使用

Immer

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();

中间件

按自己喜欢的方式管理 store

// 记录每次改变的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;

在管理中间件中使用 TS

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;

状态持久化中间件 persist

像 Redux 一样编写代码

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 事件处理程序之外调用 actions

如果在 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();
    });
};

使用 Redux 开发工具

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 } })。

React context

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>
    );
}

3. createContext 使用 props 初始化(在 TS 中)

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>
    )

TypeScript 类型定义

1. 类型定义

// 可以使用 `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})),
}));

2.使用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 相等而改变时不必要的重新渲染。

合并状态
useShallow
🚕
Page cover image