Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法
在一个典型的 React 应用中,数据是通过 props 属性自上而下进行传递的
Context 提供了一种在组件之间共享此类值的方法,而不是显式地通过组件树的逐层传递 props
如何使用Context
Context 设计目的是为了共享那些对于一个组件树而言是 全局 的数据
```jsx harmony
// 不使用 Context class App extends React.Component { render() { return ; } }
function Toolbar(props) { return ( ) }
class ThemedButton extends React.Component { render() { return } }
```jsx harmony
// Context 可以让我们无须明确地传遍每一个组件,就能将值深入传递进组件树
// 为当前的 theme 创建一个 context 默认值为 ‘light’
const ThemeContext = React.createContext('light');
class App extends React.Component {
render() {
return (
// 使用一个 Provider 来将当前的 theme 传递给以下的组件树
// 无论多深,任何组件都能读取这个值
// 在这个例子中,我们将 dark 作为当前的值传递下去
<ThemeContext.provider value='dark'>
<Toolbar/>
</ThemeContext.provider>
)
}
}
// 中间的组件再也不必指明往下传递 theme 了
function Toolbar() {
return (
<div>
<ThemedButton/>
</div>
)
}
class ThemedButton extends React.Component {
// 指定 contextType 读取当前 theme context
// React 会往上找到最近的 theme Provider,然后使用它的值
// 在这个例子中,当前的 theme 值为 dark
static contextType = ThemeContext;
render() {
return <Button theme={this.context}/>
}
}
使用Context之前的考虑
Context 主要应用场景在于很多不同层级的组件需要访问同样一些的数据
如果你只是想避免层层传递一些属性,组件组合 component composition 有时是一个比 context 更好的解决方案
```jsx harmony
// ... 渲染出 ...
// ... 渲染出 ...
// ... 渲染出 ...
* 相比较上面层层传递 props 有一种无需 context 的方法 就是 将 Avatar 组件自身传递下去
* 这种变化下,只有最顶部的 page 组件需要知道 Link 和 Avatar 组件是如何使用 user 和 avatarSize 的
* 但是这样的问题是,这并不适用于每一个场景,这种将逻辑提升到组件树的更高层次来处理
* 会使这些高层组件变的更复杂
* 并且会强行将低层组件适应这样的形式,这可能不会是你想要的
* 而且你的组件并不限制于接收单个子组件,你可能会传递多个子组件,甚至会为这些子组件 children 封装多个单独的 接口 slots
```jsx harmony
function Page(props) {
const user = props.user;
const userLink = (
<Link href={user.permalink}>
<Avatar user={user} size={props.avatarSize} />
</Link>
);
return <PageLayout userLink={userLink} />;
}
// 现在,我们有这样的组件:
<Page user={user} avatarSize={avatarSize} />
// ... 渲染出 ...
<PageLayout userLink={...} />
// ... 渲染出 ...
<NavigationBar userLink={...} />
// ... 渲染出 ...
{props.userLink}
Api
React.createContext
```jsx harmony
const MyContext = React.createContext(defaultValue);
* 创建一个 Context 对象
* 当 React 渲染一个订阅了这个 Context 对象的组件,这个组件会从组件数中离自身最近的那个匹配的 Provider 中读取当前的 context 值
* 只有当前组件所处的树中没有匹配到 Provider 时,其 defaultValue 参数才会生效。
* 有助于在不使用 Provider 包装组件的情况下对组件进行测试
* 将 undefined 传递给 Provider 的 value 时,消费组件的 defaultValue 不会生效
### Context.Provider
```jsx harmony
<MyContext.Provider value={/* value */}>
每个 Context 对象都会返回一个 Provider React 组件,它允许消费组件订阅 context 的变化
一个 Provider 可以和多个消费组件有对应关系 一个 Provider 也可以嵌套使用 里面的会覆盖外层的数据
当 Provider 的值发生变化时,它内部的所有消费组件都会重新渲染
Provider 及其内部 consumer 组件都不受制于 shouldComponentUpdate 函数
因此当 consumer 组件在其祖先组件退出更新的情况下也能更新
通过新旧值检测来确定变化,使用了 Object.is 相同的算法
Class.contextType
挂载在 class 上的 contextType 属性会被重新赋值为一个由 React.createContext() 创建的 Context 对象
这能让你使用 this.context 来消费最近的 Context 上的那个值
你可以在任何生命周期函数中访问到它,包括 render 函数中
```jsx harmony
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;
### Context.Consumer
```jsx harmony
<MyContext.Consumer>
{value => /* 基于 context 值进行渲染 */}
</MyConetxt.Consumer>
React 组件也可以订阅到 context 的变更
这个函数接收当前的 context 值,返回一个 React 节点
传递给函数的 value 值等同于往上组件树离这个 context 值最近的 Provider 提供的 value 值
如果没有相应的 Provider,value 参数等同于传递给 createContext() 的 defaultValue
Context.displayName
context 对象接受一个名为 displayName 的 property,类型为字符串
React DevTools 使用该字符串来确定 context 要显示的内容
示例
动态Context
```jsx harmony
const themes = { light: { foreground: '#000000', background: '#eeeeee' }, dark: { foreground: '#ffffff', background: '#222222' } };
const ThemeContext = React.createContext( themes.light );
class ThemedButton extends React.Component { render() { let props = this.props; let theme = this.context; return ( ) } } ThemedButton.contextType = ThemeContext;
function Toolbar(props) { return ( Change Theme ) }
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() {
return (
<div>
<ThemeContext.Provider value={this.state.theme}>
<Toolbar changeTheme={this.toggleTheme}/>
</ThemeContext.Provider>
<ThemedButton />
</div>
)
}
}
### 在嵌套组件中更新Context
* 从一个在组件树中嵌套很深的组件中更新 context 是很有必要的。在这种场景下,你可以通过 context 传递一个函数,使得 consumers 组件更新 context
```jsx harmony
const ThemeContext = React.createContext({
theme: themes.dark,
toggleTheme: () => {}
});
function ThemeTogglerButton() {
return (
<ThemeContext.Consumer>
{(theme, toggleTheme) => (
<button
onClick={toggleTheme}
style={{backgroundColor: theme.background}}
>
Toggle Theme
</button>
)}
</ThemeContext.Consumer>
)
}
function Content() {
return (
<div>
<ThemeTogglerButton />
</div>
)
}
class App extends React.Component {
constructor(props) {
super(props);
this.toggleTheme = () => {
this.setState(state => ({
theme:
state.theme === themes.dark
? theme.light
: theme.dark
}))
}
this.state = {
theme: themes.light,
toggleTheme: this.toggleTheme,
}
}
render() {
return (
<ThemeContext.Provider value={this.state}>
<Content/>
</ThemeContext.Provider>
)
}
}
消费多个Context
为了确保 context 快速进行重渲染,React需要使每一个 consumers 组件的 context 在组件树中成为一个单独的节点
```jsx harmony
const ThemeContext = React.createContext('light'); const UserContext = React.createContext({ name: 'Guest', });
function Content() { return ( {theme => ( {user => ( user: {user} theme: {theme} )} </UserContext.Consumer> )} </ThemeContext.Consumer> ) }
function Layout() { return ( ) }
class App extends React.Component { render() { const {signedInUser, theme} = this.props;
return (
<ThemeContext.Provider value={theme}>
<UserContext.Provider value={signedInUser}>
<Layout />
</UserContext.Provider>
</ThemeContext.Provider>
)
}
}
* 如果两个或更多的 context 值经常被一起使用,那你可能要考虑一下另外创建你自己的渲染组件,以提供这些值
## 注意事项
* 因为 context 会使用参考标识 reference identity 来决定何时进行渲染,这里可能会有一些陷阱
* 当 Provider 的父组件进行重渲染时,可能会在 Consumers 组件中触发意外的渲染
* 当每一次 Provider 重渲染时,以下的代码会重新渲染所有下面的 consumers 组件,因为 value 属性总是被赋值为新的对象
```jsx harmony
class App extends React.Component {
render() {
return (
<MyContext.Provider value={{something: 'something'}}>
<Toolbar />
</MyContext.Provider>
)
}
}
为了避免这种情况,将 value 状态提升到父节点的 state 里
```jsx harmony
class App extends React.Component { constructor(props) { super(props); this.state = { value: {something: 'something'} } }
render() {
return (
<Provider value={this.state.value}>
<Toolbar />
</Provider>
)
}
}
```