注意,本文仅适用于 React16 以下的版本
Context 是一个实验性的 Api,和 props 一样用于组件之间的数据传递,但是这个功能却很少使用,甚至不为人知。
为何使用 Context
在使用 React 开发的时候,通常通过改变 state 和 传递 props 对组件进行控制,特别是通过 props 在组件间的数据流传输,可以很容易的推断组件的状态。首先看一下常用的组件间 props 传递的例子:
const ParentComponent = () => { |
这个例子是 React 经典的单向数据传递的结构,数据的流向是:
ParentComponent |
React 的这个结构也使得代码非常容易进行调试和维护,当代码出现问题的时候,只需要沿着这条路径进行追踪即可。
但是,当应用变大的时候,这个中间层次可能会变多,这样当传递某个特殊的 prop 的时候,需要穿越非常多的中间组件层级,而这些中间的组件并不会用到,设置根本不知道这个 prop 的存在,这样写起来无疑非常的繁琐,在这种情况下,可以考虑使用 Context Api ,使用 Context 可以不必再中间组件传递 props ,而能让后代获取到数据:
使用 Context
使用 Context 需要增加以下属性和方法:
- 在父组件中定义方法
getChildContext,方法返回子组件接收的conext对象。 - 在父组件中定义属性
childContextType, 该属性指定了getChildContext方法返回的对象的数据类型 。 - 在想要获取
context的子组件定义属性contextTypes即可获取context,如果未定义该属性,则context是一个空对象。 - 在子组件使用
this.context获取Context,如果是无状态的SFC组件,则第二个参数传入Context。
使用 Context 重新改写上面的例子:
class ParentComponent extends React.Component { |
当 props 或者 state 改变的时候,父组件就会调用 getChildContext 方法更新 context。
组件的生命周期函数
如果为一个组件定义了 contentTypes 属性获取父组件的 contentTypes, 则以下几个生命周期函数会被传入一个额外的参数,即 context 对象:
- constructor(props, context)
- componentWillReceiveProps(nextProps, nextContext)
- shouldComponentUpdate(nextProps, nextState, nextContext)
- componentWillUpdate(nextProps, nextState, nextContext)
- componentDidUpdate(prevProps, prevState, prevContext)
Context 的缺陷
事实上,绝大多数的应用都用不到 context ,使用 context 有以下问题:
这是一个实验性的 api
这是一个实验性的 api 这就意味着这个 api 未来可能发生变动,甚至会被删除,将进一步影响到使用这个 api 的应用和组件,因此最好避免使用它。
和特定的父组件耦合
如果一个组件使用了 context , 这就意味着这个组合使用的时候就必须和一个能提供所需 context 的父组件耦合在一起,这样就限制了组件的应用范围,组件很难被复用。
context 更新被阻断
当父组件的 state 或者 pros 变化的时候,父组件的 getChildContext 会被调用更新 context,同时父组件会更新其自身和并引发其子组件的更新,而子组件接收到的 context 也会被改变,这一点到不会引起问题,在某些情况下子组件可以依赖于 context 更新自身。
但是当中间组件使用了 shouldComponentUpdate 禁止更新,这样则子组件不会被更新,子组件则不会被渲染,此时父组件更新的值无法被应用,看下面代码:
父组件 ParentComponent 加载完成后修改 state 和 context:
class ParentComponent extends React.Component { |
从上面的 log 可以看到 context 是正确更新的,这次设置中间组件禁止更新:
class MiddleChildComponent extends React.Component { |
从执行结果可以看到,第二次的 context 更新被中间组件阻断,依赖 context 进行组件的更新是有风险的。
难以维护
当维护别人的组件的时候,如果一些组件使用了 context 这样就非常的困难了,需要从大量的组件中找到要使用的组件,当项目比较大的时候无疑是非常困难的。而如果采用了 props 就可以沿着调用的方向向上找,很容易就找到定义的位置。
应用场景
Dan Abramov 设计了一些明智的规则关于使用 context:
function shouldIUseReactContextFeature() { |
上面基本已经说明了应用场景:类库作者、全局级别的信息(主题、语言、环境、用户信息等),并且在使用的时候要考虑 api 在后面版本发生修改等问题,当然也有使用 context 非常出色的类库: react-router、 react-redux。
本文最后自定义一个路由管理组件 Router 作为练习。Router 组件包含两个子组件 Router和Route 。Router 组建包括整个应用,Route` 用于匹配路径到组件。
首先定义 Router ,它提供了 Route 组件需要的几个操作,
import { Component, PropTypes } from 'react'; |
然后 Route 组件需要使用 context 中 router:
class Route extends Component { |
这样就可以在 app 入口开始使用 Router 组件:
const App = () => ( |
使用这种方式的优点在于,只要保证 Route 组件被嵌套在 Router 组件之内,Route 可以放在任意的位置,任意嵌套,都能正常使用。