原文链接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>
</>
)
}