在我看来,通过Context暴露数据或者API不是一种优雅的实践方案,尽管react-redux是这么干的。因此需要一种机制,或者说约束,去降低不必要的影响。
通过childContextTypes和contextTypes这两个静态属性的约束,可以在一定程度保障,只有组件自身,或者是与组件相关的其他子组件才可以随心所欲的访问Context的属性,无论是数据还是函数。因为只有组件自身或者相关的子组件可以清楚它能访问Context哪些属性,而相对于那些与组件无关的其他组件,无论是内部或者外部的 ,由于不清楚父组件链上各父组件的childContextTypes“声明”了哪些Context属性,所以没法通过contextTypes“申请”相关的属性。所以我理解为,给组件的作用域Context“带权限”,可以在一定程度上确保Context的可控性和影响范围。
在开发组件过程中,我们应该时刻关注这一点,不要随意的使用Context。
不需要优先使用Context作为React的高级API,React并Context。我的理解是:
Context目前还处于实验阶段,可能会在后面的发行版本中有大的变化,事实上这种情况已经发生了,所以为了避免给今后升级带来较大影响和麻烦,不建议在App中使用Context。
尽管不建议在App中使用Context,但对于组件而言,由于影响范围小于App,如果可以做到高内聚,不破坏组件树的依赖关系,那么还是可以考虑使用Context的。
对于组件之间的数据通信或者状态管理,优先考虑用props或者state解决,然后再考虑用其他第三方成熟库解决的,以上方法都不是最佳选择的时候,那么再考虑使用Context。
Context的更新需要通过setState()触发,但是这并不是可靠的。Context支持跨组件访问,但是,如果中间的子组件通过一些方法不响应更新,比如shouldComponentUpdate()返回false,那么不能保证Context的更新一定可达使用Context的子组件。因此,Context的可靠性需要关注。不过更新的问题,在新版的API中得以解决。
简而言之,只要你能确保Context是可控的,使用Context并无大碍,甚至如果能够合理的应用,Context其实可以给React组件开发带来很强大的体验。
用Context作为共享数据的媒介官方所提到Context可以用来进行跨组件的数据通信。而我,把它理解为,好比一座桥,作为一种作为媒介进行数据共享。数据共享可以分两类:App级与组件级。
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()
App的根组件用<Provider />组件包裹后,本质上就为App提供了一个全局的属性store,相当于在整个App范围内,共享store属性。当然,<Provider />组件也可以包裹在其他组件中,在组件级的全局范围内共享store。
组件级的数据共享
如果组件的功能不能单靠组件自身来完成,还需要依赖额外的子组件,那么可以利用Context构建一个由多个子组件组合的组件。例如,react-router。
react-router的<Router />自身并不能独立完成路由的操作和管理,因为导航链接和跳转的内容通常是分离的,因此还需要依赖<Link />和<Route />等子组件来一同完成路由的相关工作。为了让相关的子组件一同发挥作用,react-router的实现方案是利用Context在<Router />、<Link />以及<Route />这些相关的组件之间共享一个router,进而完成路由的统一操作和管理。
下面截取<Router />、<Link />以及<Route />这些相关的组件部分源码,以便更好的理解上述所说的。
// Router.js /** * The public API for putting history on context. */ class Router extends React.Component { static propTypes = { history: PropTypes.object.isRequired, children: PropTypes.node }; static contextTypes = { router: PropTypes.object }; static childContextTypes = { router: PropTypes.object.isRequired }; getChildContext() { return { router: { ...this.context.router, history: this.props.history, route: { location: this.props.history.location, match: this.state.match } } }; } // ...... componentWillMount() { const { children, history } = this.props; // ...... this.unlisten = history.listen(() => { this.setState({ match: this.computeMatch(history.location.pathname) }); }); } // ...... }