XXHolic / blog

I wonder how~, I wonder why~

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

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
    };
}

主要的实现思路如下:

  1. 这个插件返回了一个对象,包含了两个 React 组建: ProviderConsumer
  2. Provider 用于接受共享的数据,Consumer 用于“消费”共享的数据,从一开始就定了共享的数据 contextProp ,它们之间通过一个自定义监听对象联系在一起,使用了观察者模式。
  3. Provider 接受共享的数据时,初始化对象 emitter ,并作为 contextProp 的值向下传递。同时提供了外部对比数据的方法 calculateChangedBits
  4. Consumer 获取数据时,通过组建自带的 context 对象获取 contextProp 属性值,拿到在 Provider 中初始化的值放入 state 中,并通过 emitter 对象注册了 onUpdate 事件。
  5. Provider 组建接受的数据发生符合条件的变化时,在 Provider 的生命周期 componentWillReceiveProps 中就会触发 emitter 对象中同步数据的方法,之前 Consumeremitter 中已注册的 onUpdate 事件就会触发,获取到最新的数据,并触发 setState 方法,Consumer 子组件重新渲染,并拿到最新共享的数据。

参考资料

🗑️

继续看《黑暗的左手》,看到了一些很有意味的句子:

光明是黑暗的的左手,黑暗是光明的右手。

生死归一,如同相拥而卧的克慕恋人,如同紧握的双手,如同终点与旅程。