Bootstrap

React - useContext和深层传递参数

#题引:我认为跟着官方文档学习不会走歪路

通常来说,你会通过 props 将信息从父组件传递到子组件。但是,如果你必须通过许多中间组件向下传递 props,或是在你应用中的许多组件需要相同的信息,传递 props 会变的十分冗长和不便。Context 允许父组件向其下层无论多深的任何组件提供信息,而无需通过 props 显式传递。

假设Heading 组件接收一个level参数来决定它标题尺寸的场景

export default function Page() {
  return (
    <Section>
      <Heading level={1}>主标题</Heading>
      <Section>
        <Heading level={2}>副标题</Heading>
        <Heading level={2}>副标题</Heading>
        <Heading level={2}>副标题</Heading>
        <Section>
          <Heading level={3}>子标题</Heading>
          <Heading level={3}>子标题</Heading>
          <Heading level={3}>子标题</Heading>
          <Section>
            <Heading level={4}>子子标题</Heading>
            <Heading level={4}>子子标题</Heading>
            <Heading level={4}>子子标题</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}
export default function Section({ children }) {
  return (
    <section className="section">
      {children}
    </section>
  );
}

export default function Heading({ level, children }) {
  switch (level) {
    case 1:
      return <h1>{children}</h1>;
    case 2:
      return <h2>{children}</h2>;
    case 3:
      return <h3>{children}</h3>;
    case 4:
      return <h4>{children}</h4>;
    case 5:
      return <h5>{children}</h5>;
    case 6:
      return <h6>{children}</h6>;
    default:
      throw Error('未知的 level:' + level);
  }
}

这就是 context 大显身手的地方

Step 1:创建 context

创建这个 context,并 将其从一个文件中导出,这样你的组件才可以使用它

LevelContext.js

import { createContext } from 'react';

export const LevelContext = createContext(1);

createContext 只需默认值这么一个参数。在这里, 1 表示最大的标题级别,但是你可以传递任何类型的值(甚至可以传入一个对象)

Step 2:使用 Context

从 React 中引入 useContext Hook 以及你刚刚创建的 context

export default function Heading({ children }) {
  const level = useContext(LevelContext);
  // ...
}

useContext 告诉 React Heading 组件想要读取 LevelContext

Step 3:提供 context

Section 组件目前渲染传入它的子组件,把它们用 context provider 包裹起来 以提供 LevelContext 给它们

import { LevelContext } from './LevelContext.js';

export default function Section({ level, children }) {
  return (
    <section className="section">
      <LevelContext.Provider value={level}>
        {children}
      </LevelContext.Provider>
    </section>
  );
}

这告诉 React:“如果在 Section组件中的任何子组件请求 LevelContext,给他们这个 level。”组件会使用 UI 树中在它上层最近的那个 <LevelContext.Provider> 传递过来的值。

由于 context 让你可以从上层的组件读取信息,每个 Section 都会从上层的 Section 读取 level,并自动向下层传递 level + 1。
你可以像下面这样做:

import { useContext } from 'react';
import { LevelContext } from './LevelContext.js';

export default function Section({ children }) {
  const level = useContext(LevelContext);
  return (
    <section className="section">
      <LevelContext.Provider value={level + 1}>
        {children}
      </LevelContext.Provider>
    </section>
  );
}

这样修改之后,你不用将 level 参数传给 Section>或者是 Heading

export default function Page() {
  return (
    <Section>
      <Heading>主标题</Heading>
      <Section>
        <Heading>副标题</Heading>
        <Heading>副标题</Heading>
        <Heading>副标题</Heading>
        <Section>
          <Heading>子标题</Heading>
          <Heading>子标题</Heading>
          <Heading>子标题</Heading>
          <Section>
            <Heading>子子标题</Heading>
            <Heading>子子标题</Heading>
            <Heading>子子标题</Heading>
          </Section>
        </Section>
      </Section>
    </Section>
  );
}

在 React 中,覆盖来自上层的某些 context 的唯一方法是将子组件包裹到一个提供不同值的 context provider 中

Context 的使用场景

  • 主题: 如果你的应用允许用户更改其外观
  • 当前账户: 许多组件可能需要知道当前登录的用户信息
  • 路由: 大多数路由解决方案在其内部使用 context 来保存当前路由
  • 状态管理: 随着你的应用的增长,最终在靠近应用顶部的位置可能会有很多 state。许多遥远的下层组件可能想要修改它们。通常 将 reducer 与 context 搭配使用来管理复杂的状态并将其传递给深层的组件来避免过多的麻烦。

Context 不局限于静态值。如果你在下一次渲染时传递不同的值,React 将会更新读取它的所有下层组件。

;