译文: Zustand:究竟比React Context好在哪里?
作为一名 Web 开发者,你可能会遇到管理应用状态的情况。说到状态管理,有多种状态管理库可供选择,它们适用于不同的场景和用例。
React 内置的 Context API 很受欢迎,因为它可以直接使用,无需额外的依赖库。它可以在不使用 props 层层传递的前提下,向下传递数据(可以跨层级传参)。
然而,当涉及到更为复杂的使用场景时,我们就会开始思考使用集中式状态管理库的解决方案。
Zustand 是一个极简的依赖库,提供了一种直接管理状态的方法。在本文中,我们将通过一个示例来讨论它的优势。
场景
想象一下,我们正在一个电子商务平台上工作。
我们被指派实现一项功能:当用户购买价值超过 200 英镑的产品时,可以享受免费送货服务:

产品卡片和配送卡片来自不同的组件,我们必须在它们之间共享购物车状态。
下面是我们实现该功能的方法:
使用React Context实现
1. 创建 Context 及 Context Provider
首先,创建CartContext用来定义购物车状态及相关操作,CartContextProvider 是一个用于包裹你的一部分组件树的包裹组件,被包裹的组件都可以获取context中的数据。
import { useContext } from "react";
import { useCartStore } from "../../context/cartStore";
import { useRenderCount } from "../../hook/useRenderCount";
import { ConfigContext } from "../../context/configContext";
export const Product = () => {
  const renderCount = useRenderCount();
  const { count, setCount } = useCartStore();
  const configContext = useContext(ConfigContext);
  return (
    <div className="container">
      {configContext?.enableRenderCount && (
        <div className="render-count">{renderCount}</div>
      )}
      <div className="product-description">Rilakkuma Plush</div>
      <div className="product-image">
        <img src="./image.png" alt="rilakkuma" />
      </div>
      <div className="product-description">£49.99</div>
      <button
        onClick={() => {
          if (count > 0) setCount(count - 1);
        }}
      >
        -
      </button>
      <span className="count">{count}</span>
      <button onClick={() => setCount(count + 1)}>+</button>
    </div>
  );
};
2. 应用 Context Provider
使用 CartContextProvider 组件包裹组件树,使context在所有子组件中可用。
import { CartContextProvider } from "../../context/cartContext";
import { Product } from "./Product";
import { Checkout } from "./Checkout";
import { Delivery } from "./Delivery";
export const ReactContextDemo = () => {
  return (
    <>
      <CartContextProvider>
        <div className="layout">
          <div className="column">
            <Product />
          </div>
          <div className="column">
            <Delivery />
            <Checkout />
          </div>
        </div>
      </CartContextProvider>
    </>
  );
};
3. 使用 Context 状态数据
在子组件中使用useContext可以访问context中的数据。
import { useContext } from "react";
import { CartContext } from "../../context/cartContext";
import { useRenderCount } from "../../hook/useRenderCount";
export const Delivery = () => {
  const renderCount = useRenderCount();
  const cartContext = useContext(CartContext);
  if (cartContext == null) {
    return null;
  }
  return (
    <div className="container">
      <div className="delivery-message">
        {cartContext.deliveryCost === 0
          ? "FREE delivery"
          : `Standard delivery £${cartContext.deliveryCost}`}
      </div>
      {cartContext.deliveryCost > 0 && (
        <div className="delivery-description">
          We offer FREE DELIVERY on all orders above £200
        </div>
      )}
    </div>
  );
};
下面是代码在网页中的渲染结果,让我们看一下每个组件的重复渲染次数(红色的文字)。

当商品数量发生改变时,包括配送组件,每个组件都重新渲染了一次,即使配送组件没有更新,也进行了渲染操作。这里发生了非必要渲染。
可以使用 zustand 把这个问题处理的更好吗?
1. 安装 Zustand
通过执行如下命令,可以将 Zustand 添加到项目中。
yarn add zustand
2. 创建 Store
在 Zustand 中,store 用于保存应用程序状态。使用 Zustand 提供的 create 方法来创建 store ,并定义 state 和 action。
import { create } from "zustand";
interface CountState {
  count: number;
  setCount: (count: number) => void;
  deliveryCost: number;
  subTotal: number;
  payTotal: number;
}
const DEFAULT_DELIVERY_COST = 4.99;
const PRODUCT_PRICE = 49.99;
export const useCartStore = create<CountState>((set) => ({
  count: 0,
  setCount: (count: number) =>
    set(() => {
      const subTotal = Math.round(count * PRODUCT_PRICE * 100) / 100;
      const deliveryCost = subTotal > 200 ? 0 : DEFAULT_DELIVERY_COST;
      const payTotal = Math.round((subTotal + deliveryCost) * 100) / 100;
      return {
        count,
        subTotal,
        deliveryCost,
        payTotal,
      };
    }),
  deliveryCost: 0,
  subTotal: 0,
  payTotal: 0,
}));
3. 在组件中使用 Store
可以通过调用CartStore.tsx导出的自定义 hook,来使用 Zustand 中的 state。
可以使用 Zustand 提供的 useShallow hook 避免非必要的重复渲染,只有state.deliveryCost发生变化时,组件才会重新渲染。
import { useContext } from "react";
import { useCartStore } from "../../context/cartStore";
import { useShallow } from "zustand/react/shallow";
export const Delivery = () => {
  // re-renders the component only when state.deliveryCost change
  const { deliveryCost } = useCartStore(
    useShallow((state) => ({ deliveryCost: state.deliveryCost }))
  );
  return (
    <div className="container">
      <div className="delivery-message">
        {deliveryCost === 0
          ? "FREE delivery"
          : `Standard delivery £${deliveryCost}`}
      </div>
      {deliveryCost > 0 && (
        <div className="delivery-description">
          We offer FREE DELIVERY on all orders above £200
        </div>
      )}
    </div>
  );
};
下面是代码在网页中的渲染结果,让我们看一下每个组件的重复渲染次数(红色的文字)。

正如你所看到的,配送组件只有在展示免费送货时才会重新渲染,这就展示了Zustand如何避免重复渲染的。
结论
虽然 React Context 提供了一种将状态传递给子组件的原生解决方案,但是 Zustand 在一些场景下,可以提供更好的性能。例如上面的场景,组件只有在其使用到的特定状态发生变化时才会触发重新渲染。
希望这篇文章能够让你了解 Zustand 如何比 React Context 更好的提高应用程序性能。
转载自:https://juejin.cn/post/7395862719809290290