Context:
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
Context 提供了一种在组件之间共享此类值的方式,而不必显式地通过组件树的逐层传递 props。
1. 何时使用Context
Context 设计目的是为了共享那些对于一个组件树而言是“全局”的数据。
例如当前认证的用户、主题或首选语言。
举个例子,在下面的代码中,我们通过一个 “theme” 属性手动调整一个按钮组件的样式:
- App.js
import './App.css';
import Toolbar from './component/Toolbar';
function App() {
return (
<div className="App">
<Toolbar theme='dark'/>
</div>
);
}
export default App;
- Toolbar.js
import ThemedButton from './ThemedButton';
function Toolbar(props){
return (
<>
<ThemedButton theme={props.theme}/>
</>
)
}
export default Toolbar;
- ThemedButton.js
import React from 'react';
class ThemedButton extends React.Component {
render(){
return <button theme={this.props.theme}></button>
}
}
export default ThemedButton;
Toolbar 组件接受一个额外的“theme”属性,然后传递给 ThemedButton 组件。
如果应用中每一个单独的按钮都需要知道 theme 的值,这会是件很麻烦的事,
因为必须将这个值层层传递所有组件。
使用context,避免通过中间元素传递props:
- App.js
import './App.css';
import Toolbar from './component/Toolbar';
import React from 'react';
// Context可以让我们无需明确地传遍每一个组件,就能将值深入传递进组件树。
// 为当前的theme创建一个context('light'为默认值)
export const ThemeContext = React.createContext('light');
function App() {
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树。
// 无论多深,任何组件都能读取这个值。
// 在这个例子中,我们将 “dark” 作为当前的值传递下去。
return (
<div className="App">
<ThemeContext.Provider value='dark'>
<Toolbar theme='dark'/>
</ThemeContext.Provider>
</div>
);
}
export default App;
- Toolbar.js
import ThemedButton from './ThemedButton';
// 中间的组件再也不必指明往下传递 theme 了。
function Toolbar(){
return (
<>
<ThemedButton/>
</>
)
}
export default Toolbar;
- ThemedButton.js
import React from 'react';
import ThemeContext from '../App';
class ThemedButton extends React.Component {
// 指定 contextType 读取当前的 theme context。
// React 会往上找到最近的 theme Provider,然后使用它的值。
// 在这个例子中,当前的 theme 值为 “dark”。
static contextType = ThemeContext;
render(){
return <button theme={this.context}></button>
}
}
export default ThemedButton;
2. API
2.1 React.createContext
const MyContext = React.createContext(defaultValue);
- 创建一个Context对象。
- 当React渲染一个组件(订阅了Context对象),这个组件会从组件树中离自身最近的匹配的Provider中读取当前context值。
- 只有当组件所处的树没有匹配到Provider时,defaultValue参数才会生效。(此默认值有助于在不使用 Provider 包装组件的情况下对组件进行测试。)
- 注意:将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效。
2.2 Context对象.Provider
<MyContext.Provider value={/* 某个值 */}>
- 每一个Context对象都会返回一个Provide React组件,它允许消费组件订阅context的变化。
- Provider接收一个value属性,传递给消费组件。
- 一个Provider可以和多个消费组件有对应关系。多个Provider也可以嵌套使用,里层的会覆盖外层的数据。
- 当Provider的value值发生变化时,它内部的所有消费组件都会重新渲染。
- 从 Provider 到其内部 consumer 组件(包括 .contextType 和 useContext)的传播不受制于 shouldComponentUpdate 函数,因此当 consumer 组件在其祖先组件跳过更新的情况下也能更新。
- 通过新旧值检测来确定变化。
2.3 Class.contextType
类组件的使用方式,函数组件使用钩子函数useContext()
南栀的博客–Hooks
class MyClass extends React.Component {
componentDidMount() {
let value = this.context;
/* 在组件挂载完成后,使用 MyContext 组件的值来执行一些有副作用的操作 */
}
componentDidUpdate() {
let value = this.context;
/* ... */
}
componentWillUnmount() {
let value = this.context;
/* ... */
}
render() {
let value = this.context;
/* 基于 MyContext 组件的值进行渲染 */
}
}
MyClass.contextType = MyContext;
- 挂载在class上的contextType属性可以赋值为由React.createContext()创建的Context对象。
- contextType可以让你使用this.context来获取最近Context上的值。你可以在任何生命周期中访问到它,包括 render 函数中。
2.4 Context对象.Consumer
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染*/}
</MyContext.Consumer>
- 一个 React 组件可以订阅 context 的变更,此组件可以让你在函数式组件中可以订阅 context。
- 这种方法需要一个函数作为子元素(function as a child)。这个函数接收当前的 context 值,并返回一个 React 节点。
- 传递给函数的 value 值等价于组件树上方离这个 context 最近的 Provider 提供的 value 值。
- 如果没有对应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue。
2.5 Context对象.displayName
context 对象接受一个名为 displayName 的property,类型为字符串。
React DevTools 使用该字符串来确定 context 要显示的内容。
const MyContext = React.createContext(/* some value */);
MyContext.displayName = 'MyDisplayName';
<MyContext.Provider> // "MyDisplayName.Provider" 在 DevTools 中
<MyContext.Consumer> // "MyDisplayName.Consumer" 在 DevTools 中
3. 示例
3.1 动态Context
- theme-context.js
import React from 'react';
export const themes = {
light: {
background:'#eeeeee',
foreground:'#000000',
},
dark: {
background:'#222222',
foreground:'#ffffff',
},
};
export const ThemeContext = React.createContext(themes.dark);
- themed-button.js
import {ThemeContext} from '../theme-context';
import React from 'react';
class ThemedButton extends React.Component {
render(){
let props = this.props;
let theme = this.context;
return(
<button
{...props} style={{background:theme.background}}>
</button>
)
}
}
ThemedButton.contextType = ThemeContext;
export default ThemedButton;
- App.js
import './App.css';
import {ThemeContext,themes} from './theme-context';
import ThemedButton from './component/themed-button';
import React from 'react';
// 一个使用 ThemedButton 的中间组件
function Toolbar(props){
return (
<ThemedButton onClick={props.changeTheme}>
Change Theme
</ThemedButton>
);
}
class App extends React.Component {
constructor(props) {
super(props);
this.state = {
theme: themes.light,
};
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? themes.light
: themes.dark,
}));
};
}
render() {
// 在 ThemeProvider 内部的 ThemedButton 按钮组件使用 state 中的 theme 值,
// 而外部的组件使用默认的 theme 值
return (
<>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme} />
</ThemeContext.Provider>
<>
<ThemedButton />
</>
</>
);
}
}
export default App;
参考文档:React官方文档