React 开发 - 认识 Zustand

我们参考文章 React 开发 - 初始化项目 来创建一个名为 zustand-demo 的项目。并进行初始化的改造👇

zustand 是个小巧,快速和可扩展的 React 应用程序的状态管理的库。它有以下的优点:

  • 简单的 API
  • 最小的模版代码
  • 更好的性能
  • 支持 TypeScript
  • 中间件支持

假设现在我们已经安装了 zustand - yarn add zustand。我们来介绍如何使用它👇

创建 store

我们的 store 就是一个 hook。我们可以在里面放置基本类型数据,对象,函数。状态必须以不可变的方式更改,并且 set 函数会合并状态。

tsx 复制代码
// src/stores/bearStore.tsx

import { create } from 'zustand'

type TBearStoreState = {
  bears: number;
  increasePopulation: () => void;
  removeAllBears: () => void;
}

export const useBearStore = create<TBearStoreState>((set) => ({
  bears: 0,
  increasePopulation: () => set((state) => ({ bears: state.bears + 1 })),
  removeAllBears: () => set({ bears: 0 }),
}))

然后我们来验证下:

tsx 复制代码
// components/BearBox.tsx

import React from "react";
// 引入 useBearStore 这个hook
import { useBearStore } from "../stores/bearStore";

export const BearBox = () => {
  // 获取到状态中的 bears
  const bears = useBearStore(state => state.bears);
  // 增加
  const increasePopulation = useBearStore(state => state.increasePopulation);
  // 删除
  const removeAllBears = useBearStore(state => state.removeAllBears);

  return (
    <div>
      <h1>Bear Box</h1>
      <p>bears: {bears}</p>
      <div>
        <button onClick={increasePopulation}>Add Bear</button>
        <button onClick={removeAllBears}>Remove Bear</button>
      </div>
    </div>
  );
}

get() 和 set()

这里我们介绍 get()set() 这两个函数。

我们创建另外一个 store 来了解👇 set() 用来更改状态, get() 获取获获取状态。

tsx 复制代码
// src/stores/catStore.tsx

import { create } from "zustand";

type TCatStoreState = {
  cats: {
    bigCats: number;
  };
  increaseBigCats: () => void;
 }

export const useCatStore = create<TCatStoreState>() ((set) => ({
  cats: {
    bigCats: 0,
  },
  increaseBigCats: () => {
    set(
      (state) => ({
        // set 函数会自动帮我们合并第一层级的状态,但是第二层的状态就需要我们使用 ... 扩展符来提取状态
        cats: {
          ...state.cats,
          bigCats: state.cats.bigCats + 1
        }
      })
    )
  }
}))

我们在上面的 set 函数中,更改了 cats 中的状态。set 函数会自动帮我合并第一层级的状态,但是其他层级就需要自己手动更改。

有时候,我们需要在 set 函数之外 获取到 state 的数据,那么我们就可以使用 get 函数。如下另外一种获取 Big Cats 的方法:

tsx 复制代码
// src/stores/catStore.tsx

type TCatStoreState = {
  // ...
  getBigCatsOutSideSetFn: () => void;
}

export const useCatStore = create<TCatStoreState>() ((set, get) => ({
  cats: {
    bigCats: 0,
    smallCats: 0,
  },
  // 通过 get() 函数在 set() 函数外获取 state 的状态
  getBigCatsOutSideSetFn: () => {
    const bigCatsNumber = get().cats.bigCats;
    return `There are ${bigCatsNumber} big cats.`;
  }
}))

使用 immer 更改状态

在上面我们也知道,**set** 函数会自动帮我合并第一层级的状态,其他的状态需要我们手动调整。如果状态是有多层级的数据,那么,我们将望而止步。还好,immer 这个中间件能够帮助我们实现这个功能。

通过 yarn add immer 来安装依赖。

我们在原先的案例中更改:

tsx 复制代码
// src/stores/catStore.tsx

import { create } from "zustand";

type TCatStoreState = {
  cats: {
    smallCats: {
      name: string;
      // other
    };
    bigCats: number;
    // other
  };
  increaseBigCats: () => void;
 }

export const useCatStore = create<TCatStoreState>() ((set) => ({
  cats: {
    bigCats: 0,
    smallCats: {
      name: "hello kitty"
    },
  },
  increaseBigCats: () => {
    set(
      (state) => ({
        cats: {
          ...state.cats,
          bigCats: state.cats.bigCats + 1,
          smallCats: {
            ...state.cats.smallCats,
            name: "hello jimmy"
          }
        }
      })
    )
  }
}))

没有使用 immer 之前,我们需要对更改的层级进行 ... 扩展符进行提取。引入之后,就简单了,我们只需要更改关注的值就行了:

tsx 复制代码
// src/stores/catStore.tsx

import { immer } from "zustand/middleware/immer";

export const useCatStore = create<TCatStoreState>() (immer((set) => ({
  cats: {
    bigCats: 0,
    smallCats: {
      name: "hello kitty"
    },
  },
  increaseBigCats: () => {
    set(
      (state) => ({
        cats: {
          bigCats: state.cats.bigCats + 1,
          smallCats: {
            name: "hello jimmy"
          }
        }
      })
    )
  }
})))

使用 selector

seletor 允许我们在调用 set() 函数的时候,只更新状态的一部份,而不是整个状态的对象。这有助于提高性能。

比如,一个页面两个组件中,一个组件调用了 cat 中的 smallCats,另一个组件中国调用了 bigCats,但是我们在更改 smallCats 中的状态时候,却影响到了另一个组件带有 bigCats 的组件重新渲染。这是因为之前我们在更改 smallCats 的时候,更改了全部状态数据 cats

我们参考 Auto Generating Selectors 来创建 Selectors

typescript 复制代码
// src/utils/createSelector.ts

import { StoreApi, UseBoundStore } from 'zustand'

type WithSelectors<S> = S extends { getState: () => infer T }
  ? S & { use: { [K in keyof T]: () => T[K] } }
  : never

export const createSelectors = <S extends UseBoundStore<StoreApi<object>>>(
  _store: S,
) => {
  const  store = _store as WithSelectors<typeof _store>
  store.use = {}
  for (const k of Object.keys(store.getState())) {
    (store.use as any)[k] = () => store((s) => s[k as keyof typeof s])
  }

  return store
}

结合,我们将 selector 整合到 store 上,这里以 catStore 为例:

tsx 复制代码
// src/stores/catStore.tsx

import { createSelectors } from "../utils/createSelector";

// ...

export const useCatStore = createSelectors(
  create<TCatStoreState>() (immer((set) => ({
    cats: {
      bigCats: 0,
      smallCats: {
        name: "hello kitty"
      },
    },
    increaseBigCats: () => {
      set(
        (state) => ({
          cats: {
            bigCats: state.cats.bigCats + 1,
            smallCats: {
              name: "hello jimmy"
            }
          }
        })
      )
    }
  })))
);

然后,我们就可以通过 useCatStore.use. 来调用。比如,只是出发点更改 increaseBigCats

tsx 复制代码
// src/components/CatBox.tsx

// const increateBigCats = useCatStore(state => state.increaseBigCats);

  const increateBigCats = useCatStore.use.increaseBigCats();
  
  return (
    <div>
      <h1>Cat Box</h1>
      <p>big cats: {bigCats} - {name}</p>
      <div>
        <button onClick={increateBigCats}>Add Big Cat</button>
      </div>
    </div>
  );

此时,页面中我们只是更改了 big cats,其他没有使用到 big cats 的组件不会重新渲染。

嗯,zustand 的相关用法就聊到这里,感谢阅读【✅】🌹

参考

相关推荐
乔峰不是张无忌33019 分钟前
【HTML】动态闪烁圣诞树+雪花+音效
前端·javascript·html·圣诞树
码农老起26 分钟前
掌握 React:组件化开发与性能优化的实战指南
react.js·前端框架
鸿蒙自习室26 分钟前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
m0_7482507433 分钟前
高性能Web网关:OpenResty 基础讲解
前端·openresty
前端没钱1 小时前
从 Vue 迈向 React:平滑过渡与关键注意点全解析
前端·vue.js·react.js
汪洪墩1 小时前
【Mars3d】设置backgroundImage、map.scene.skyBox、backgroundImage来回切换
开发语言·javascript·python·ecmascript·webgl·cesium
NoneCoder1 小时前
CSS系列(29)-- Scroll Snap详解
前端·css
无言非影1 小时前
vtie项目中使用到了TailwindCSS,如何打包成一个单独的CSS文件(优化、压缩)
前端·css
我曾经是个程序员2 小时前
鸿蒙学习记录
开发语言·前端·javascript
羊小猪~~2 小时前
前端入门之VUE--ajax、vuex、router,最后的前端总结
前端·javascript·css·vue.js·vscode·ajax·html5