likes
comments
collection
share

译文: Zustand:究竟比React Context好在哪里?

作者站长头像
站长
· 阅读数 28

作为一名 Web 开发者,你可能会遇到管理应用状态的情况。说到状态管理,有多种状态管理库可供选择,它们适用于不同的场景和用例。

React 内置的 Context API 很受欢迎,因为它可以直接使用,无需额外的依赖库。它可以在不使用 props 层层传递的前提下,向下传递数据(可以跨层级传参)。

然而,当涉及到更为复杂的使用场景时,我们就会开始思考使用集中式状态管理库的解决方案。

Zustand 是一个极简的依赖库,提供了一种直接管理状态的方法。在本文中,我们将通过一个示例来讨论它的优势。

场景

想象一下,我们正在一个电子商务平台上工作。

我们被指派实现一项功能:当用户购买价值超过 200 英镑的产品时,可以享受免费送货服务:

译文: Zustand:究竟比React Context好在哪里?

产品卡片和配送卡片来自不同的组件,我们必须在它们之间共享购物车状态。

下面是我们实现该功能的方法:

使用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:究竟比React Context好在哪里?

当商品数量发生改变时,包括配送组件,每个组件都重新渲染了一次,即使配送组件没有更新,也进行了渲染操作。这里发生了非必要渲染。

可以使用 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如何避免重复渲染的。

结论

虽然 React Context 提供了一种将状态传递给子组件的原生解决方案,但是 Zustand 在一些场景下,可以提供更好的性能。例如上面的场景,组件只有在其使用到的特定状态发生变化时才会触发重新渲染。

希望这篇文章能够让你了解 Zustand 如何比 React Context 更好的提高应用程序性能。

演示源码 原文链接 如侵则删

转载自:https://juejin.cn/post/7395862719809290290
评论
请登录