How to use React Context & How to useContext in React【译】

514 阅读6分钟

原文链接www.robinwieruch.de/react-useco… & www.robinwieruch.de/react-conte…

React Context 是一个强大的功能。 如果您的 React 应用程序的规模超出了一个小型应用程序的范畴,那么不妨尝试一下。 在 React 生态上许多像 Redux 这样的第三方库都在幕后使用它,赶快来学一下它吧!

如果您的组件层次结构在垂直方向上增长,则将多个 React 组件向下传递 props 会变得冗余——从父组件到深度嵌套的子组件。 大多数情况下,中间的所有 React 组件都对这些 props 并不消费,只是将 props 传递给下一个子组件,直到它到达所需的子组件。

本教程为您提供了如何将 React Context 用于一个简单用例。

REACT CONTEXT: How

假设组件树的结构是A -> B -> C ,我们需要在组件A中提供向下的数据。

首先,必须创建 React Context 本身,通过它可以访问 Provider 和 Consumer 组件。 使用 createContext 创建上下文时可以向它传递一个初始值。 初始值也可以为空。

// src/ThemeContext.js
 
import React from 'react';
 
const ThemeContext = React.createContext(null);
 
export default ThemeContext;

其次,组件 A 必须为给定的 Provider 组件提供上下文。 在这种情况下,它的值会立即赋予它所需的组件上,它可以是从组件状态(例如获取的数据)到 props 的任何内容。 如果该值来自可修改的 React 状态,则传递给 Provider 组件的值也可以更改。

// src/ComponentA.js
 
import React from 'react';
import ThemeContext from './ThemeContext';
 
const A = () => (
  <ThemeContext.Provider value="green">
    <B />
  </ThemeContext.Provider>
);

组件 A 只显示组件 B,虽然没有向它传递任何道具,而是让值 green 可用于下面的所有 React 组件。 子组件之一将是最终使用上下文的组件 C。

第三,在组件 C 中,在组件 B 下方,您可以使用上下文对象。 请注意,组件 A 不需要通过 props 中的组件 B 向下传递任何内容,以便它到达组件 C。

// src/ComponentC.js
 
import React from 'react';
import ThemeContext from './ThemeContext';
 
const C = () => (
  <ThemeContext.Consumer>
    {value => (
      <p style={{ color: value }}>
        Hello World
      </p>
    )}
  </ThemeContext.Consumer>
);

组件可以通过使用上下文来派生其样式。 Consumer 组件通过使用渲染道具使传递的上下文可用。 可以想象,按照这种方式,每个需要根据主题设置样式的组件现在都可以通过使用 ThemeContext 的 Consumer 组件从 React 的 Context 中获取必要的信息。 只需要使用 Provider 组件,该组件在它们上方的某处传递一次值。

REACT CONTEXT: WHEN

什么时候应该使用 React Context? 一般来说,有两个用例何时使用它:

  • 当您的 React 组件层次结构在大小上垂直增长,并且您希望能够将 props 传递给子组件而不会打扰其间的组件。 我们在整个 React Context 教程中都使用了这个用例作为示例。
  • 当您希望在 React 中使用 React Hooks 进行高级状态管理以通过 React 应用程序通过 React Context 传递状态和状态更新器函数时。 通过 React Context 执行此操作允许您创建共享和全局状态。

REACT'S USECONTEXT HOOK

ThemeContext.Provider 不变,用 useContext 改写上述ThemeContext.Consumer 例子:

// src/ComponentC.js
 
import React from 'react';
import ThemeContext from './ThemeContext';
 
const C = () => {
    const value = React.useContext(ThemeContext);
    return (
      <p style={{ color: value }}>
        Hello World
      </p>
    )
};

React 的 useContext Hook 将 Context 作为参数从中检索值。 使用 React Hook 而不是 Consumer 组件可以使代码更具可读性,更简洁,并且不会在两者之间引入组件(这里是 Consumer 组件)。

STATEFUL CONTEXT IN REACT WITH USECONTEXT

在前面的示例中,上下文是一个静态(或无状态)值。 但在大多数用例中,上下文将用于传递有状态的值。 我们现在将解决这个问题,因为用户可能想要看到可变的主题色。

const A = () => {
  const [color, setColor] = React.useState('green');
 
  return (
    <ThemeContext.Provider value={color}>
      <button type="button" onClick={() => setColor('red')}>
        Red Color
      </button>
      <button type="button" onClick={() => setColor('yellow')}>
        Yellow Color
      </button>
 
      <B />
      <D />
    </ThemeContext.Provider>
  );
};

通过单击其中一个按钮,内联事件处理程序将更改有状态值。 因为在状态更改后会发生重新渲染,所以修改后的值通过 Provider 组件传递给所有子组件,这些子组件将其显示为动态值。 这里需要思考的一个问题是重新渲染之后组件D也跟着重新渲染了,这并不是我们需要的结果,因为D中根本没有用这个值。

BEST PRACTICES FOR CONTEXT AND USECONTEXT

在使用 React Context 和 useContext 时,可以遵循一些最佳实践。 到目前为止,您已经了解了基础知识。 本节通过向您展示如何在更大的 React 项目中使用上下文来超越这些基础知识,并试图解决上面遗留的一个重复渲染的问题。

当我为 React Context 创建一个新文件时,我总是从基本要素开始(如前所述):

// src/ThemeContext.js
import React from 'react';
 
const ThemeContext = React.createContext(null);
 
export { ThemeContext };

首先,我想改进的是提供一个自定义上下文挂钩来访问上下文:

// src/ThemeContext.js
import React from 'react';
 
const ThemeContext = React.createContext(null);
 
const useColor = () => React.useContext(ThemeContext);
 
export { ThemeContext, useColor };

然后我使用这个新的自定义上下文挂钩,而不必使用 useContext 作为中介:

// src/ComponentC.js
 
import React from 'react';
import { ThemeContext, useColor } from './ThemeContext';
 
const C = () => {
    const value = useColor();
    return (
      <p style={{ color: value }}>
        Hello World
      </p>
    )
};

或者,如果我必须在第三方(如 Styled Components)中使用上下文,我会暴露一个 HOC:

// src/ThemeContext.js
import React from 'react';
 
const ThemeContext = React.createContext(null);
 
const useColor = () => React.useContext(ThemeContext);
 
const withColor = (Component) => (props) => {
  const value = useColor();
 
  return <Component {...props} value={value} />;
};
 
// if ref is used
//
// const withColor = (Component) =>
//   React.forwardRef((props, ref) => {
//     const value = useColor();
 
//     return <Component {...props} ref={ref} value={value} />;
//   });
 
export { ThemeContext, useColor, withColor };

第三,与自定义上下文钩子类似,也可以提供自定义Provider组件:

// src/ThemeContext.js
import React from 'react';
 
const ThemeContext = React.createContext(null);
 
const useColor = () => React.useContext(ThemeContext);
 
const ColorProvider = ({ value, children }) => {
  return (
    <ThemeContext.Provider value='green'>
      {children}
    </ThemeContext.Provider>
  );
};
 
export { ColorProvider, useColor };

请注意,不再导出 ThemeContext 本身。 相反,它是新的自定义 Provider 组件,它在 A 组件中使用并且仍然接收有状态值:

// src/ComponentA.js
 
import React from 'react';
import { ColorProvider, useColor } from './ThemeContext';
 
const A = () => {
    const [color, setColor] = React.useState('green');
    return (
      <ColorProvider value={color}>
        <B />
      </ColorProvider>
    )
};

至此,已经完全将ThemeContext 封装起来了,我们只需将颜色字典和状态加入即可,完整的代码如下:

import React from 'react';
const ThemeContext = React.createContext(null);
const COLORS = {
  GREEN: 'green',
  RED: 'red',
  YELLOW: 'yellow'
};
const ColorProvider = ({ children }) => {
  const [color, setColor] = React.useState(COLORS.GREEN);
  return (
    <CurrencyContext.Provider value={[color, setColor]}>
      {children}
    </CurrencyContext.Provider>
  );
};
const useColor = () => {
  const [color, setColor] = React.useContext(ThemeContext);
  const handleColor = (value) => {
    setColor(value);
  };
  return { value: color, onChange: handleColor };
};
export { ColorProvider, useColor, COLORS };

就是这样! 我们将状态和状态更新逻辑封装到我们的自定义 Provider 组件和自定义上下文挂钩中。 使用这个新 API 的任何人都可以访问状态和一个函数,以在他们的 React 应用程序中的整个组件树中更新它。 在 A 组件中使用:

import React from 'react';
import B from './B';
import { ColorProvider, useColor, COLORS } from './ThemeContext';
 
export default A = () => (
  <ColorProvider>
    <B />
  </ColorProvider>
);

在 C 组件中使用:

import React from 'react';
import { ColorProvider, useColor, COLORS } from './ThemeContext';
 
export default D = () => {
	const { value, onChange } = useColor()
	return (
		<>
		<button type="button" onClick={() => onChange(COLORS.RED)}>
			Red Color
		</button>
		<button type="button" onClick={() => onChange(COLORS.YELLOW)}>
			Yellow Color
		</button>
        <p style={{ color: value }}>
        	Hello World
        </p>
		</>
	)
}