认识React Context

前言

React Context的概念, 前两天学习React Router时再次接触到

1
2
3
4
5
6
7
8
9
10
11
12
13
14
export default React.createClass({

// ask for `router` from context
contextTypes: {
router: React.PropTypes.object
},

// ...

handleSubmit(event) {
// ...
this.context.router.push(path)
},
})

于是决定要搞明白这是个什么东东

解惑

一般而言, React中的父组件要传props给子组件, 必须显示地指定props的传递. 当组件层级过多时, 你可能觉得这样好麻烦, 于是React 提供了Context特性供你解决这个问题

只要父组件在Context定义了数据, 那么其下的所有子组件都可以使用Context里的数据

在组件周期函数(lifecycle methods)里可以通过this.context来获取Context, 经测试, 在constructor里是不可以的

父组件定义Context

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

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

childContextTypes不可少, 否则getChildContext将报错childContextTypes must be defined in order to use getChildContext()

子组件使用Context

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
class Child extends React.Component {
// 也可以在这里写contextTypes
static contextTypes = {
color: React.PropTypes.string
}
render() {
return <button style={{background: this.context.color}}>{this.props.text}</button>
}
}

// 无状态函数组件也可以, context 作为最后一个参数
const Child = ({children}, context) => (
<button style={{background: context.color}}>{children}</button>
)

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

contextTypes不可少, 否则子组件this.context为空对象, 却不会报错
另外, 我发现在contextTypeschildContextTypes里面, 类型的指定可以直接使用js对象, 如下:

1
2
3
Child.contextTypes = {
color: String
};

建议

React的一个优点就是, 单向数据流, 数据的传递是清晰可见的; 使用了Context的, 数据的传递就变得隐晦起来了, 其实这就相当于在变相地使用全局变量

有人说, props的逐层传递, 当组件层级深了, 数据也不清晰啊. 其实这个问题可以借助工具解决的, 使用react-devtools, 可以清楚地看到组件的stateprops; 但是使用工具, 却是无法看到Context的数据的, 这的确是一个问题

官方建议适合使用Context的来来传递的数据应该是: 登录后的用户信息, 当前语言种类, 或主题信息

因为Context 是在React v0.14 版本以后发布的一个实验性的功能,官方有提醒其API在未来有可能会更改, 因此在实践中除了上述情况外, 最好避免使用它. 在此我也就简单地介绍一下, 更多详情请看官方文档

参考资料

附录

前言中代码里, 为什么Route里的组件可以从Context获取router对象呢? 这是因为React Router会创建RouterContext, 作为所有Route的父组件, 而RouterContext有如下代码, 定义了Context:

1
2
3
4
5
6
7
8
9
10
childContextTypes: {
history: object,
location: object.isRequired,
router: object.isRequired
},

getChildContext: function getChildContext() {
// ..
return { history: history, location: location, router: router };
}
Fork me on GitHub