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.Provider 和 AgeContext.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