Read mini-create-react-context
XXHolic opened this issue · comments
目录
引子
在看 react-router 中相关的代码时,发现在版本 v5.1.2 **享数据使用的不是新 Context API,而是使用了 mini-create-react-context 。看了源码后,在此记录一下。
查看 mini-create-react-context 源码版本 0.3.2 。
简介
Context 的作用在官方文档中这样描述:
Context 提供了一个无需为每层组件手动添加 props,就能在组件树间进行数据传递的方法。
在 React 16.3 版本中开始引入新的 Context API。
源码
源码是 ts 格式,转换成了新语法的 js 格式。
import { Component } from 'react';
import PropTypes from 'prop-types';
import gud from 'gud'; // 用于创建唯一的 ID
import warning from 'tiny-warning';
const MAX_SIGNED_31_BIT_INT = 1073741823;
// Inlined Object.is polyfill.
// https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
function objectIs(x, y) {
if (x === y) {
return x !== 0 || 1 / x === 1 / y;
} else {
return x !== x && y !== y;
}
}
// Provider 和 Consumer 之间数据同步的桥梁
function createEventEmitter(value) {
let handlers = [];
return {
on(handler) {
handlers.push(handler);
},
off(handler) {
handlers = handlers.filter(h => h !== handler);
},
get() {
return value;
},
set(newValue, changedBits) {
value = newValue;
handlers.forEach(handler => handler(value, changedBits));
}
};
}
function onlyChild(children) {
return Array.isArray(children) ? children[0] : children;
}
export default function createReactContext(defaultValue, calculateChangedBits) {
const contextProp = '__create-react-context-' + gud() + '__';
class Provider extends Component {
constructor() {
super(...arguments);
this.emitter = createEventEmitter(this.props.value);
}
// 当 state 或者 props 改变的时候,getChildContext 函数就会被调用
getChildContext() {
return {
[contextProp]: this.emitter
};
}
componentWillReceiveProps(nextProps) {
if (this.props.value !== nextProps.value) {
let oldValue = this.props.value;
let newValue = nextProps.value;
let changedBits;
if (objectIs(oldValue, newValue)) {
changedBits = 0; // No change
} else {
changedBits =
typeof calculateChangedBits === 'function'
? calculateChangedBits(oldValue, newValue)
: MAX_SIGNED_31_BIT_INT;
if (process.env.NODE_ENV !== 'production') {
warning((changedBits & MAX_SIGNED_31_BIT_INT) === changedBits, 'calculateChangedBits: Expected the return value to be a ' +
'31-bit integer. Instead received: ' + changedBits);
}
changedBits |= 0;
if (changedBits !== 0) {
this.emitter.set(nextProps.value, changedBits);
}
}
}
}
render() {
return this.props.children;
}
}
// 共享的数据
Provider.childContextTypes = {
[contextProp]: PropTypes.object.isRequired
};
class Consumer extends Component {
constructor() {
super(...arguments);
this.state = {
value: this.getValue()
};
this.onUpdate = (newValue, changedBits) => {
const observedBits = this.observedBits | 0;
if ((observedBits & changedBits) !== 0) {
this.setState({ value: this.getValue() });
}
};
}
componentWillReceiveProps(nextProps) {
let { observedBits } = nextProps;
this.observedBits =
observedBits === undefined || observedBits === null
? MAX_SIGNED_31_BIT_INT // Subscribe to all changes by default
: observedBits;
}
componentDidMount() {
// 注册事件
if (this.context[contextProp]) {
this.context[contextProp].on(this.onUpdate);
}
let { observedBits } = this.props;
this.observedBits =
observedBits === undefined || observedBits === null
? MAX_SIGNED_31_BIT_INT // Subscribe to all changes by default
: observedBits;
}
componentWillUnmount() {
if (this.context[contextProp]) {
this.context[contextProp].off(this.onUpdate);
}
}
getValue() {
if (this.context[contextProp]) {
return this.context[contextProp].get();
} else {
return defaultValue;
}
}
render() {
return onlyChild(this.props.children)(this.state.value);
}
}
// 如果 contextTypes 没有被定义,context 就是个空对象,会导致获取不到相关值
Consumer.contextTypes = {
[contextProp]: PropTypes.object
};
return {
Provider,
Consumer
};
}
主要的实现思路如下:
- 这个插件返回了一个对象,包含了两个 React 组建: Provider 和 Consumer 。
- Provider 用于接受共享的数据,Consumer 用于“消费”共享的数据,从一开始就定了共享的数据
contextProp
,它们之间通过一个自定义监听对象联系在一起,使用了观察者模式。 - Provider 接受共享的数据时,初始化对象
emitter
,并作为contextProp
的值向下传递。同时提供了外部对比数据的方法calculateChangedBits
。 - Consumer 获取数据时,通过组建自带的
context
对象获取contextProp
属性值,拿到在 Provider 中初始化的值放入state
中,并通过emitter
对象注册了onUpdate
事件。 - 当 Provider 组建接受的数据发生符合条件的变化时,在 Provider 的生命周期
componentWillReceiveProps
中就会触发emitter
对象中同步数据的方法,之前 Consumer 在emitter
中已注册的onUpdate
事件就会触发,获取到最新的数据,并触发setState
方法,Consumer 子组件重新渲染,并拿到最新共享的数据。