WHAT - React Context 两层组件的优化机制

目录

一、Context

官方文档:https://react.dev/learn/passing-data-deeply-with-context

二、分离逻辑和视图

在 React 中使用两层组件来使用 Context 的原因主要是为了分离逻辑和视图,并且更好地管理和优化状态。

这种模式通常被称为"Context Provider/Consumer"模式。

以下是一些具体原因:

  1. 解耦状态逻辑和视图逻辑

    • Context Provider 组件通常负责管理和提供状态。它包含与状态管理相关的逻辑,例如获取数据、处理状态变化等。
    • Consumer 组件 负责渲染与状态相关的视图。它通过 Context 访问 Provider 提供的状态,从而保持视图逻辑的简洁和易维护。
  2. 提高组件的可复用性

    • 使用 Provider 和 Consumer 分离逻辑和视图后,可以更容易地在不同的视图组件中复用相同的状态逻辑,而无需重复编写相同的代码。
  3. 优化性能

    • Context 的更新会触发使用了这个 Context 的所有组件重新渲染。通过分层结构,可以在 Provider 中集中处理状态更新,减少不必要的重新渲染,提高性能。
    • Consumer 组件可以根据需要细粒度地订阅 Context 的某些部分,避免不必要的渲染。
  4. 简化测试

    • 逻辑和视图的分离使得单元测试更加简单。Provider 组件可以独立测试其状态管理逻辑,而 Consumer 组件可以通过模拟的 Context 值来测试其渲染逻辑。
  5. 增强代码的可维护性和可读性

    • 使用两层组件可以使代码结构更加清晰,职责分离明确。状态管理和 UI 逻辑分别处理,使得代码更容易理解和维护。

一个典型的使用示例如下:

jsx 复制代码
// 创建 Context
const MyContext = React.createContext();

// Provider 组件
const MyProvider = ({ children }) => {
  const [state, setState] = useState(initialState);

  const value = {
    state,
    updateState: (newState) => setState(newState),
  };

  return (
    <MyContext.Provider value={value}>
      {children}
    </MyContext.Provider>
  );
};

// Consumer 组件
const MyConsumer = () => {
  const { state, updateState } = useContext(MyContext);

  return (
    <div>
      <p>Current state: {state}</p>
      <button onClick={() => updateState("new state")}>Update State</button>
    </div>
  );
};

// 使用 Provider 包裹应用
const App = () => (
  <MyProvider>
    <MyConsumer />
  </MyProvider>
);

export default App;

通过这种方式,Provider 组件管理状态逻辑,Consumer 组件专注于渲染逻辑,从而实现了状态逻辑和视图逻辑的分离。

三、优化性能

  • Context 的更新会触发使用了这个 Context 的所有组件重新渲染。通过分层结构,可以在 Provider 中集中处理状态更新,减少不必要的重新渲染,提高性能。
  • Consumer 组件可以根据需要细粒度地订阅 Context 的某些部分,避免不必要的渲染。

这段话解释了 Context 的工作机制以及如何优化组件的渲染行为。具体来说,当 Context 的值发生变化时,所有使用 useContext 获取该 Context 的组件都会重新渲染,即使这些组件使用了 React.memoshouldComponentUpdate 来优化性能。这是因为 Context 的变化会绕过这些优化机制。

为了避免这种情况,可以将组件分为两个部分:一个外层组件从 Context 中读取所需的内容,并将这些内容作为 props 传递给使用 React.memo 优化的子组件。这样,只有当与子组件相关的 Context 值发生变化时,子组件才会重新渲染。

下面是一个示例代码和解释:

示例代码

jsx 复制代码
import React, { useContext, useState, createContext, memo } from 'react';

// 创建 Context
const MyContext = createContext();

const MyProvider = ({ children }) => {
  const [state, setState] = useState({ count: 0, name: 'John' });

  return (
    <MyContext.Provider value={{ state, setState }}>
      {children}
    </MyContext.Provider>
  );
};

// 外层组件,从 Context 中读取所需内容
const OuterComponent = () => {
  const { state } = useContext(MyContext);
  
  // 只提取与子组件相关的值
  const name = state.name;

  return <InnerComponent name={name} />;
};

// 子组件,使用 memo 优化
const InnerComponent = memo(({ name }) => {
  console.log('InnerComponent render');
  return <div>Name: {name}</div>;
});

const App = () => (
  <MyProvider>
    <OuterComponent />
    <button onClick={() => setState((prev) => ({ ...prev, count: prev.count + 1 }))}>
      Increment Count
    </button>
  </MyProvider>
);

export default App;

解释

  1. 创建 Context :我们创建了一个 Context MyContext,并使用 MyProvider 组件来提供 Context 的值。

  2. MyProvider 组件MyProvider 组件管理状态 state,包含 countname,并将其提供给 Context。

  3. OuterComponent 组件OuterComponent 组件使用 useContext 钩子从 Context 中读取状态 state。然后,它提取出与子组件相关的 name 值,并将其作为 props 传递给 InnerComponent

  4. InnerComponent 组件InnerComponent 组件使用 React.memo 进行优化。由于它只接收 name 作为 props,因此只有在 name 发生变化时,它才会重新渲染。

  5. 避免不必要的重新渲染 :通过将 OuterComponent 作为中间层,从 Context 中读取值并将其传递给子组件,可以避免 InnerComponentcount 值变化时重新渲染,因为 InnerComponentcount 的变化不感兴趣。

这样,通过将组件分为两个部分,确保只有与子组件相关的 Context 值变化时才会触发子组件的重新渲染,从而优化性能。

相关推荐
糯米团子749几秒前
react速通-3
javascript·react.js·前端框架
心连欣6 分钟前
从静态页面到动态交互:DOM操作的核心API解析
前端·javascript·api
橙某人9 分钟前
SSR页面上的按钮点不了?Nuxt 懒加载水合揭秘💧
前端·vue.js·nuxt.js
PursuitofHappiness18 分钟前
2 tree-cli 的使用方法
前端
不做超级小白21 分钟前
把图片压小,但不糊:reduceUrImgs项目关键点拆解
前端·开源·node.js
耀耀切克闹灬23 分钟前
Eruda 移动端调试工具使用指南
前端
王二端茶倒水31 分钟前
现在AI Agent 已经能够代替程序员的工作了,作为一个程序员的我该如何规划以后的职业,请认真思考后给我最靠谱可行的建议。
前端·后端·面试
CyrusCJA33 分钟前
毛玻璃效果
前端·css·css3
光影少年35 分钟前
Monorepo架构是什么,如何学习Monorepo架构?
前端·学习·架构·前端框架
yuandiv38 分钟前
让 Playwright 测试管理更优雅的利器
前端