Context
被翻译为上下文,在编程领域,这是一个经常会接触到的概念,React中也有。
在React的官方文档中,Context
被归类为高级部分(Advanced),属于React的高级API,但官方并不建议在稳定版的App中使用Context。
Context
。事实上,很多优秀的React组件都通过Context来完成自己的功能,比如react-redux的
,就是通过
Context
提供一个全局态的
store
,拖拽组件react-dnd,通过
Context
在组件中分发DOM的Drag和Drop事件,路由组件react-router通过
Context
管理路由状态等等。在React组件开发中,如果用好
Context
,可以让你的组件变得强大,而且灵活。
props
或者
state
的方式来传递数据时,可以使用
Context
来实现跨层级的组件数据传递。
Context
,可以跨越组件进行数据传递。
Context
生产者,需要通过一个静态属性
childContextTypes
声明提供给子组件的
Context
对象的属性,并实现一个实例
getChildContext
方法,返回一个代表
Context
的纯对象 (plain object) 。
而对于
Context
的消费者,通过如下方式访问父组件提供的
Context
。
子组件需要通过一个静态属性contextTypes
声明后,才能访问父组件Context
对象的属性,否则,即使属性名没写错,拿到的对象也是undefined
。
对于无状态子组件(Stateless Component),可以通过如下方式访问父组件的Context
通过静态方法React.createContext()
创建一个Context
对象,这个Context
对象包含两个组件,
和
。
几个可以直接获取Context的地方
实际上,除了实例的context
属性(this.context
),React组件还有很多个地方可以直接访问父组件提供的Context
。比如构造方法:
constructor(props, context)
比如生命周期:
componentWillReceiveProps(nextProps, nextContext)
shouldComponentUpdate(nextProps, nextState, nextContext)
componetWillUpdate(nextProps, nextState, nextContext)
Context
。
const StatelessComponent = (props, context) => ( ...... )
把Context当做组件作用域
使用React的开发者都知道,一个React App本质就是一棵React组件树,每个React组件相当于这棵树上的一个节点,除了App的根节点,其他每个节点都存在一条父组件链。
Context
暴露数据或者API不是一种优雅的实践方案,尽管react-redux是这么干的。因此需要一种机制,或者说约束,去降低不必要的影响。
通过childContextTypes
和contextTypes
这两个静态属性的约束,可以在一定程度保障,只有组件自身,或者是与组件相关的其他子组件才可以随心所欲的访问Context
的属性,无论是数据还是函数。因为只有组件自身或者相关的子组件可以清楚它能访问Context
哪些属性,而相对于那些与组件无关的其他组件,无论是内部或者外部的 ,由于不清楚父组件链上各父组件的childContextTypes
“声明”了哪些Context
属性,所以没法通过contextTypes
“申请”相关的属性。所以我理解为,给组件的作用域Context
“带权限”,可以在一定程度上确保Context
的可控性和影响范围。
在开发组件过程中,我们应该时刻关注这一点,不要随意的使用Context
。
- App级的数据共享
App根节点组件提供的Context
对象可以看成是App级的全局作用域,所以,我们利用App根节点组件提供的Context
对象创建一些App级的全局数据。现成的例子可以参考react-redux,以下是
组件源码的核心实现:
export function createProvider(storeKey = 'store', subKey) { const subscriptionKey = subKey || `${storeKey}Subscription` class Provider extends Component { getChildContext() { return { [storeKey]: this[storeKey], [subscriptionKey]: null } } constructor(props, context) { super(props, context) this[storeKey] = props.store; } render() { return Children.only(this.props.children) } } // ...... Provider.propTypes = { store: storeShape.isRequired, children: PropTypes.element.isRequired, } Provider.childContextTypes = { [storeKey]: storeShape.isRequired, [subscriptionKey]: subscriptionShape, } return Provider } export default createProvider()
组件包裹后,本质上就为App提供了一个全局的属性
store
,相当于在整个App范围内,共享
store
属性。当然,
组件也可以包裹在其他组件中,在组件级的全局范围内共享
store
。