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 的相关用法就聊到这里,感谢阅读【✅】🌹

参考

相关推荐
jokerest1238 分钟前
web——upload-labs——第三关——后缀黑名单绕过
前端
星之卡比*14 分钟前
前端知识点---this的用法 , this动态绑定(Javascript)
开发语言·前端·javascript
爱学习的小康27 分钟前
使用pdfmake导出pdf文件
javascript·node.js·angular.js
摇光9329 分钟前
[前端面试]javascript
前端·javascript·面试
chusheng184033 分钟前
基于Java Web 的家乡特色菜推荐系统
java·开发语言·前端·springboot·家乡特色菜推荐系统·家乡特色菜推荐
流情1 小时前
html+js+css实现拖拽式便签留言
javascript·css·html
nameofworld1 小时前
前端面试笔试(三)
前端·面试·学习方法·promise·dom
喵不拉几1 小时前
Flowable 构建后端服务(后端以及数据库搭建) & Flowable Modeler 设计器搭建(前端)
前端·数据库
老码沉思录2 小时前
React Native 全栈开发实战班 - 用户界面之手势系统应用
react native·react.js·ui