React全新的Context API

Context 设计目的是为共享那些被认为对于一个组件树而言是“全局”的数据,例如当前认证的用户、主题或首选语言。官方文档特别提醒说不要仅仅为了避免在几个层级下的组件传递 props 而使用 context,它是被用于在多个层级的多个组件需要访问相同数据的情景。

16.3之前的 Context API 的使用场景以及缺陷

该版本已经不建议使用

我们都知道在 React 中父子组件可以通过 props 自顶向下的传递数据。但是当组件深度嵌套时,从顶层组件向最内层组件传递数据就不那么方便了。手动在每一层组件上逐级传递 prop 不仅书写起来很繁琐同时还会为夹在中间的组件引入不必要的 prop。这时 Context API 就派上用场了。你只需要在外层组件上声明要传递给子组件的 Context:

注意:由于React v15.5开始 React.PropTypes已经废弃,现在需要单独使用prop-types来定义contextTypes。
通过在 Parent 上添加 childContextTypes 和 getChildContext , React会向下自动传递参数,任何组件只要在它的子组件中就能通过定义contextTypes来获取参数。
代码清单1

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class Parent extends React.Component {
getChildContext() {
return {color: "purple"};
}
render() {
return (
<div>
<Child text={message.text} />
</div>
);
}
}

Parent.childContextTypes = {
color: PropTypes.string
};

然后就可以在任意一级子组件上访问 Context 里的内容了:
代码清单2

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
import PropTypes from "prop-types";
import React from "react";

class Child extends React.Component {
render() {
return (
<button style={{background: this.context.color}}>
我是按钮
</button>
);
}
}

Child.contextTypes = {
color: PropTypes.string
};

// 上面的代码也可以通过无状态组件实现
import PropTypes from "prop-types";
import React from "react";

const Button = ({children}, context) =>(
<button style={{background: context.color}}>
我是按钮
</button>
);

Child.contextTypes = {
color: PropTypes.string
};

这个版本的 Context API 和 shouldComponentUpdate 搭配使用时很容易出问题。让我们通过一个小例子来简单说明,假设有以下组件结构:

1
2
3
4
5
<A>
<B>
<C />
</B>
</A>

其中组件 A 会通过 getChildContext 设置 Context,组件 C 通过 this.context 读取 Context。

当组件 A 要更新 Context 的时候发生什么呢?

  1. 组件 A 通过 setState 设置新的 Context 值同时触发子组件的 rerender。
  2. 组件 B rerender。
  3. 组件 C rerender,并在自己的 render 方法中拿到更新后的Context。

整个流程看起来好像没什么问题。如果我们在组件 B 上定义了 shouldComponentUpdate 会发生什么呢?

  1. 组件 A 通过 setState 设置新的 Context 值同时触发子组件的 rerender。
  2. 组件 B 执行 shouldComponetUpdate,由于组件 B 自身并不依赖 Context,所以 shouldComponetUpdate 检测到 state 与 prop 均未变化因此返回 false。无需重新 render。
  3. 由于 B 组件没有 rerender。这导致组件 C 也不会rerender,因此也就无法获取到最新的 Context 值。
  4. 由于 shouldComponentUpdate 是一个 React 开发人员经常使用的优化方法。所以如果代码里使用了这一版的 Context API 很大概率会遇到上述问题。

PropTypes 属性验证在打包为生产环境的时候一般会选择除去,这种使用方式也会产生选择的困难。

16.3新版Context API

新版 Context API 都由以下几部分组成:

  1. React.createContext 方法用于创建一个 Context 对象。该对象包含 Provider 和 Consumer两个属性,分别为两个 React 组件。
  2. Provider 组件。用在组件树中更外层的位置。它接受一个名为 value 的 prop,其值可以是任何 JavaScript 中的数据类型。
  3. Consumer 组件。可以在 Provider 组件内部的任何一层使用。它接收一个名为 children 值为一个函数的 prop。这个函数的参数是 Provider 组件接收的那个 value prop 的值,返回值是一个 React 元素(一段 JSX 代码)。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
let ThemeContext = React.createContext({
style: {
color: "green",
background: "red",
}
});

function Button({ theme, ...props }) {
return (
<button {...props} >我是按钮</button>
);
}


function ThemedButton(props) {
return (
/*
* Consumer会自动获取离其最近的Provider提供的value
* */
<ThemeContext.Consumer>
{
(context) => {
return <Button {...props} style={context.style} />;
}
}
</ThemeContext.Consumer>
);
}

function Toolbar(props) {
return (
<ThemeContext.Provider value={{
style: {
color: "red",
background: "green",
}
}}>
<ThemedButton />
</ThemeContext.Provider>
);
}

ReactDOM.render(
<Toolbar />,
document.getElementById("root")
);

这版 Context API 的几个特点:

  1. Provider 和 Consumer 必须来自同一次 React.createContext 调用。也就是说 NameContext.ProviderAgeContext.Consumer 是无法搭配使用的。
  2. React.createContext 方法接收一个默认值作为参数。当 Consumer 外层没有对应的 Provider 时就会使用该默认值。
  3. Provider 组件的 value prop 值发生变更时,其内部组件树中对应的 Consumer 组件会接收到新值并重新执行 children 函数。此过程不受 shouldComponentUpdete 方法的影响。
  4. Provider 组件利用 Object.is 检测 value prop 的值是否有更新。注意 Object.is 和 === 的行为不完全相同。具体细节请参考 Object.is 的 MDN 文档页。
  5. Consumer 组件接收一个函数作为 children prop 并利用该函数的返回值生成组件树的模式被称为 Render Props 模式。

注意: 因为 context 使用引用标示符(reference identity)来判断何时需要重新渲染,所以有些情况下,当 provider 的父元素重新渲染时,会触发 consumer 的非内部渲染。为了避免这个问题,可以将 value 放在 render 的外部,如 state

通过高阶函数简化

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { ThemeContext } from "./themeContext";
import React from "react";
export function withTheme(Component) {
return function (props) {
return (
<ThemeContext.Consumer>
{
(context) => {
return <Component {...props} style={context.style} />;
}
}
</ThemeContext.Consumer>
);
};
}

16.6 提供了便利的API

为了简化 Context API的使用,React 16.6的版本新提供了一个contextType来简化使用

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import { ThemeContext } from "./themeContext";
import React from "react";

class Button extends React.Component {

static contextType = ThemeContext;

render() {
return (
<button onClick={this.context.change} style={this.context.style}>我是按钮3333</button>
);
}
}

export { Button };

通过这种方式,每个组件只能注册一个context对象。如果需要读取多个context的value值,参加Consuming Multiple Contexts.

参考

http://www.cnblogs.com/qiqi105/p/8881097.html