注意,本文仅适用于 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
可以放在任意的位置,任意嵌套,都能正常使用。