对react的context的研究
Context
被翻译为上下文,在编程领域,这是一个经常会接触到的概念,React中也有。
在React的官方文档中,Context
被归类为高级部分(Advanced),属于React的高级API,但官方并不建议在稳定版的App中使用Context。
Context
。事实上,很多优秀的React组件都通过Context来完成自己的功能,比如react-redux的<Provider />
,就是通过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
对象包含两个组件,<Provider />
和<Consumer />
。
几个可以直接获取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,以下是<Provider />
组件源码的核心实现:
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()
<Provider />
组件包裹后,本质上就为App提供了一个全局的属性store
,相当于在整个App范围内,共享store
属性。当然,<Provider />
组件也可以包裹在其他组件中,在组件级的全局范围内共享store
。