简介
在典型的 React 应用程序中,数据通过 props 自上而下(父到子)传递,但对于应用程序中许多组件所需的某些类型的 props(例如环境偏好,UI主题),这可能很麻烦。 上下文(Context) 提供了在组件之间共享这些值的方法,而不必在树的每个层级显式传递一个 prop 。
API
React.createContext
const {Provider, Consumer} = React.createContext(defaultValue);
创建一个 { Provider, Consumer } 对。当 React 渲染 context Consumer 时,它将从组件树中匹配最接近的 Provider 中读取当前的 context 值。
defaultValue 参数 仅 当 Consumer(使用者) 在树中没有匹配的 Provider(提供则) 时使用它。这有助于在不封装它们的情况下对组件进行测试。注意:将 undefined 作为 Provider(提供者) 值传递不会导致 Consumer(使用者) 使用 defaultValue 。
Provider
<Provider value={/* some value */}>
React组件允许 Consumer(使用者) 订阅 context 的改变。
接受一个 value 属性传递给 Provider(提供者) 的后代的 Consumer(使用者) 。 一个 Provider 可以连接到许多 Consumers 。 Providers 可以被嵌套以覆盖树中更深层次的值。
Consumer
<Consumer>
{value => /* render something based on the context value */}
</Consumer>
一个可以订阅 context 变化的 React 组件。
需要接收一个 函数作为子节点。 该函数接收当前 context 值并返回一个 React 节点。 传递给函数的 value 参数将等于组件树上层 context 中最接近的 Provider 的 value 属性。 如果上层没有提供这个 context 的 Provider ,value参数将等于传递给 createContext() 的 defaultValue 。
只要 Provider 的 value 属性发生变化是,所有属于该 Provider 后代的 Consumers 就会重新渲染。 从 Provider 到它的后代 Consumers 的传播不受 shouldComponentUpdate 方法的约束, 所以即使当祖先组件退出更新时,后代 Consumer 也会被更新。
通过使用与Object.is相同的算法比较新值和旧值来确定 value 属性变化。
注意 当传递对象作为
value时,在确定value属性是否变化时引发一些问题:Caveats。
何时使用
Context 旨在共享一个组件树内可被视为 “全局” 的数据,例如当前经过身份验证的用户,主题或首选语言等。 例如,在下面的代码中,我们通过一个”theme” 属性(prop) 来手动创建 Button 组件的样式:
class App extends React.Component {
render() {
return <Toolbar theme="dark" />;
}
}
function Toolbar(props) {
// 这个组件必须传递一个 "theme" prop 给 ThemeButton。
// 如果应用中每一个按钮都需要定义“theme”
// 那我们是不是需要通过prop传递给所有的按钮?
// 特别是对于嵌套层级深的组件,这个过程显然很繁琐。
return (
<div>
<ThemedButton theme={props.theme} />
</div>
);
}
function ThemedButton(props) {
return <Button theme={props.theme} />;
}
使用 context, 我们可以避免通过中间元素传递 props:
// Context 可以让我们深度传递一个值
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
// 通过 Context.Provider 这个值,无论组件嵌套的多深,都可以读到这个 theme
return (
<ThemeContext.Provider value="dark">
<Toolbar />
</ThemeContext.Provider>
);
}
}
// 这个父组件不一定要有 "theme" prop
function Toolbar(props) {
return (
<div>
<ThemedButton />
</div>
);
}
function ThemedButton(props) {
// 通过 Context.Consumer ThemedButton 可以直接读到这个值
// 它会去查找最近的 Provider
return (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
}
从嵌套组件更新 context
我们通常需要从组件树中深层嵌套组件中更新 context。 在这种情况下,您可以在 context 中向下传递一个函数,以允许 Consumer 更新 context :
theme-context.js
export const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {},
});
theme-toggler-button.js
import {ThemeContext} from './theme-context';
function ThemeTogglerButton() {
// 不仅拿到了 "theme" 也拿到了 "toggleTheme"
return (
<ThemeContext.Consumer>
{({theme, toggleTheme}) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
);
}
export default ThemeTogglerButton;
app.js
import {ThemeContext, themes} from './theme-context';
import ThemeTogglerButton from './theme-toggler-button';
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
// State also contains the updater function so it will
// be passed down into the context provider
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
};
}
render() {
// The entire state is passed to the provider
return (
<ThemeContext.Provider value={this.state}>
<Content />
</ThemeContext.Provider>
);
}
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
);
}
ReactDOM.render(<App />, document.root);
在生命周期方法中访问 Context
在生命周期方法中访问 context 值是一种相对常见的用例。 不是将 context 添加到每个生命周期方法中, 你只需将它作为 props , 然后像使用 props 一样使用它即可。
class Button extends React.Component {
componentDidMount() {
// ThemeContext value is this.props.theme
}
componentDidUpdate(prevProps, prevState) {
// Previous ThemeContext value is prevProps.theme
// New ThemeContext value is this.props.theme
}
render() {
const {theme, children} = this.props;
return (
<button className={theme ? 'dark' : 'light'}>
{children}
</button>
);
}
}
export default props => (
<ThemeContext.Consumer>
{theme => <Button {...props} theme={theme} />}
</ThemeContext.Consumer>
);
性能优化
因为 context 使用引用标识来确定何时重新渲染, 当 Provider(提供者) 的父节点重新渲染时,有可能触发 Consumer(使用者) 无意渲染。 例如,下面的代码将在每次 Provider(提供者) 重新渲染时,会重新渲染所有 Consumer(使用者) ,因为总是为 value 创建一个新对象:
class App extends React.Component {
render() {
return (
<Provider value={{something: 'something'}}>
<Toolbar />
</Provider>
);
}
}
为了防止这样, 提升 value 到父节点的 state 里:
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
value: {something: 'something'},
};
}
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
);
}
}