我们为什么使用context?
- context提供了一种直接传递数据到组件树深处组件的方式,数据不需要一层一层传递下去。
用法
改写父组件:添加
getChildContext方法和childContextTypes属性123456789101112class ParentComponent extends React.Component {getChildContext() {return {color: "purple"};}render() {// ....}}ParentComponent.childContextTypes = {color: PropTypes.string}改写子组件:添加
contextTypes属性,并通过this.context访问属性如果没有定义
contextTypes,this.context为空对象123ChildComponent.contextTypes = {color: PropTypes.string}如果定义了
contextTypes,子组件的如下生命周期函数将会接收context作为参数1234constructor(props, context)componentWillReceiveProps(nextProps, nextContext)shouldComponentUpdate(nextProps, nextState, nextContext)componentWillUpdate(nextProps, nextState, nextContext)如果定义了
contextTypes,无状态组件也会接收context作为参数123function ChildComponent (props, context) {//...}
使用context存在的问题
context + shouldComponentUpdate/PureComponent
shouldComponentUpdate的作用:当state没有改变时,控制组件以及其子组件不重新渲染- 使用shouldComponentUpdate或者使用PureComponent时,阻止子组件选择的同时也会阻止
context的传递
如何安全的使用context
指定一定的规则
- 规定
context是不可变的,并且只在初始化时向下传递一次
使用 injection system
- 使用一个特殊的数据结构,监听state的变化,强制更新组件。参考这个库:react-broadcast,它使用了这种模式。12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455// 数据结构如下class Theme {constructor(color) {this.color = colorthis.subscriptions = [] // listeners}setColor(color) {this.color = colorthis.subscriptions.forEach(f => f()) // 触发listener}subscribe(f) {this.subscriptions.push(f) // 注册listener}}// 在父组件上只要color变化,调用setColorclass MessageBox extends React.Component {constructor (props, context) {super (props, context)this.theme = new Theme(this.props.color) // 实例化Theme}// 只要color有更新,就调用setColorcomponentWillReceiveProps(next) {this.theme.setColor(next.color)}getChildContext() {return {theme: this.theme};}render () {return <MessageList messages={this.props.messages}/>}}MessageBox.childContextTypes = {theme: PropTypes.object}// 在子组件上注册listener,并通过this.context.theme访问colorclass Button extends React.Component {componentDidMount () {// 注册listenerthis.context.theme.subscribe(() => this.forceUpdate())}render() {return (<button style={{background: this.context.theme.color}}>{this.props.children}</button>);}}Button.contextTypes = {theme: PropTypes.object};
上述实现过于简单,一个合格的实现需要在
componentWillUnmount中清除事件监听器,并且更新state应该使用setState而不是forceUpdate
react如何实现context
ReactFiberContext.js
isContextProvider:通过判断父组件是否定义了
childContextTypes,来确定父组件是否是一个contextProvider123export function isContextProvider(fiber: Fiber): boolean {return fiber.tag === ClassComponent && fiber.type.childContextTypes != null;}isContextConsumer:通过判断组件是否定义了
contextTypes,来确定组件是否是一个contextConsumer123export function isContextConsumer(fiber: Fiber): boolean {return fiber.tag === ClassComponent && fiber.type.contextTypes != null;}processChildContext:通过父组件获取子组件的context
1234567891011121314151617181920212223export function processChildContext(fiber: Fiber,parentContext: Object,): Object {const instance = fiber.stateNode;const childContextTypes = fiber.type.childContextTypes;// 声明子组件的context变量let childContext;startPhaseTimer(fiber, 'getChildContext');// 通过getChildContext获取对象赋给childContextchildContext = instance.getChildContext();stopPhaseTimer();// 判断getChildContext中定义的对象,是否有在childContextTypes中声明for (let contextKey in childContext) {invariant(contextKey in childContextTypes,'%s.getChildContext(): key "%s" is not defined in childContextTypes.',getComponentName(fiber) || 'Unknown',contextKey,);}return {...parentContext, ...childContext};}getMaskedContext:在子组件上获取context
1234567891011121314151617181920212223242526272829export function getMaskedContext(workInProgress: Fiber,unmaskedContext: Object,) {const type = workInProgress.type;// 子组件必须定义contextTypes,否则会返回一个空对象const contextTypes = type.contextTypes;if (!contextTypes) {return emptyObject;}// 当context没有变化时,从备份中获取contextconst instance = workInProgress.stateNode;if (instance &&instance.__reactInternalMemoizedUnmaskedChildContext === unmaskedContext) {return instance.__reactInternalMemoizedMaskedChildContext;}// 创建contextconst context = {};for (let key in contextTypes) {context[key] = unmaskedContext[key];}// 备份contextif (instance) {cacheContext(workInProgress, unmaskedContext, context);}return context;}
ReactFiberReconciler.js
更新的时候,子组件会重新获取它的context
12345678910111213141516updateContainer(element: ReactNodeList,container: OpaqueRoot,parentComponent: ?React$Component<any, any>,callback: ?Function,): void {// ...// 获取子组件的contextconst context = getContextForSubtree(parentComponent);if (container.context === null) {container.context = context;} else {container.pendingContext = context;}// ...}根据父组件获取子组件的context
12345678910111213function getContextForSubtree(parentComponent: ?React$Component<any, any>,): Object {// 如果没有父组件,context为emptyObjectif (!parentComponent) {return emptyObject;}const fiber = ReactInstanceMap.get(parentComponent);const parentContext = findCurrentUnmaskedContext(fiber);return isContextProvider(fiber)? processChildContext(fiber, parentContext): parentContext;}
ReactFiberBeginWork.js
子组件更新context在这个进行
待续。。。