阅读 230

mini-create-react-context源码分析

官网描述该第三方包是React.createContextponyfill(用于兼容低版本的react)。与create-react-context体积对比:

originalmini
install size50 kB140 kB
minified3.3 kB2.3kB
minzip1.3 kB1.0kB

过时的ReactContext API

源码分析

src/index.ts

import React from 'react';
import createReactContext from './implementation';

// 默认导出React.createContext,当React.createContext不存在的时候(低版本的react),导出createReactContext
export default React.createContext || createReactContext;
复制代码

src/index.d.ts

类型文件

import * as React from 'react';

// 第一个参数为默认值,calculateChangedBits用来控制订阅组件的渲染
export default function createReactContext<T>(
	defaultValue: T,
	calculateChangedBits?: (prev: T, next: T) => number
): Context<T>;

// Consumer接受一个函数作为子元素,并且返回一个ReactNode
type RenderFn<T> = (value: T) => React.ReactNode;

// Context 包含一个Provider用于发布和Consumer用于订阅
export type Context<T> = {
	Provider: React.ComponentClass<ProviderProps<T>>;
	Consumer: React.ComponentClass<ConsumerProps<T>>;
};

// Provider props 接受一个value 和 children
export type ProviderProps<T> = {
	value: T;
	children?: React.ReactNode;
	observedBits?: any,
};

// Consumer props 接受一个RenderFn类型的children,observedBits用于控制Consumer子组件的渲染
export type ConsumerProps<T> = {
	children: RenderFn<T> | [RenderFn<T>];
	observedBits?: number;
};

复制代码

src/implementation.ts

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import warning from 'tiny-warning';

// 转换为2进制=111111111111111111111111111111
const MAX_SIGNED_31_BIT_INT = 1073741823;

// globalThis的polyfill,用于访问当前的全局上下问环境
const commonjsGlobal: any =
// globalThis 标准属性,用于访问当前环境的全局对象
    typeof globalThis !== 'undefined' // 'global proper'
    ? globalThis
	// 浏览器环境,返回window对象
    : typeof window !== 'undefined' 
        ? window // Browser
		// nodejs 环境,返回global
        : typeof global !== 'undefined'
            ? global // node.js
            : {}

// 从全局上下文获取名为__global_unique_id__的值
function getUniqueId() {
    const key = '__global_unique_id__';
    return commonjsGlobal[key] = (commonjsGlobal[key] || 0) + 1;
}

// Inlined Object.is polyfill.
// Object.is的polyfill,React.createContext的更新比较是通过Object.is这个api来实现的
// 与全等 === 的主要区别在于,+0 -0不相等,NaN 和 NaN 相等。== 进行不同类型的比较时,会先转化为number类型
// 这段代码来自mdn,https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is
function objectIs(x: any, y: any) {
	if (x === y) {
		// 如果x和y都不为0时,返回true
		// 如果x和y都为0并且是相同符号,返回true
		// 1/-0 -Infinity 1/+0 Infinity
		// 用于 +0 -0校验
		return x !== 0 || 1 / x === 1 / y;
	} else {
		// 如果x,y都为NaN,返回true。用于 NaN 和 NaN校验
		return x !== x && y !== y;
	}
}

// 声明ConsumerState类型,包含一个value属性
export type ConsumerState<T> = {
	value: T
};

// 声明RenderFn类型,接受一个value属性并返回一个ReactNode
type RenderFn<T> = (value: T) => React.ReactNode;

// 声明Context,包含Provider和Consumer两个ReactComponent类型的属性
export type Context<T> = {
	Provider: React.ComponentClass<ProviderProps<T>>;
	Consumer: React.ComponentClass<ConsumerProps<T>>;
};

// 声明Provider接收的props的类型
export type ProviderProps<T> = {
	value: T;
	children?: React.ReactNode;
	observedBits?: any,
};

//  声明Consumer接收的props的类型
export type ConsumerProps<T> = {
	children: RenderFn<T> | [RenderFn<T>];
	observedBits?: number;
};


// 创建一个EventEmitter函数
function createEventEmitter(value: any) {
	let handlers: any[] = [];
	return {
		// 收集事件
		on(handler: any) {
			handlers.push(handler);
		},
		// 注销事件
		off(handler: any) {
			handlers = handlers.filter(h => h !== handler);
		},
		// 获取value值
		get() {
			return value;
		},
		// 设置value值时同时触发当前注册的监听函数
		set(newValue: any, changedBits: any) {
			value = newValue;
			handlers.forEach(handler => handler(value, changedBits));
		}
	};
}
// 判断children是否是一个数组,如果是,只返回第一个子元素。用在Consumer中
function onlyChild(children: any): any {
	return Array.isArray(children) ? children[0] : children;
}

// calculateChangedBits 用来控制是否需要渲染,返回值为0的时候,不渲染订阅的子组件
export default function createReactContext<T>(defaultValue: T, calculateChangedBits?: (a: T, b: T) => number): Context<T> {
	// __create-react-context-0__ 
	// getUniqueId() 默认从0开始,每次新建context的时候+1
	const contextProp = '__create-react-context-' + getUniqueId() + '__';

	class Provider extends Component<ProviderProps<T>> {
		// 创建一个依赖收集器,并在每次this.props.value变动的时候,触发事件
		emitter = createEventEmitter(this.props.value);

		// 过时api,该属性配合getChildContext可以实现React 自动向下传递信息,自组件通过定义 contextTypes 来访问 context
		static childContextTypes = {
			[contextProp]: PropTypes.object.isRequired
		};

		// 过时api,当 state 或者 props 改变的时候,getChildContext会被调用。
		// 高版本的react不应该使用此api,因为高版本React.createContext的更新并不受shouldComponentUpdate的限制,而当前函数很明显会受到shouldComponentUpdate的影响
		getChildContext() {
			return {
				// 返回当前实例的emitter
				[contextProp]: this.emitter
			};
		}

		componentWillReceiveProps(nextProps: any) {
			if (this.props.value !== nextProps.value) {
				// 保存引用
				let oldValue = this.props.value;
				let newValue = nextProps.value;
				// 判断是否需要更新,0不更新,不为0更新
				let changedBits: number;

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

					// 按位或运算 |0 给changedBits取整数
					changedBits |= 0;

					if (changedBits !== 0) {
						// 触发更新
						this.emitter.set(nextProps.value, changedBits);
					}
				}
			}
		}

		// 返回当前的子元素
		render() {
			return this.props.children;
		}
	}

	class Consumer extends Component<ConsumerProps<T>, ConsumerState<T>> {
		// 定义contextTypes用于接收Provider提供的context上下文对象
		static contextTypes = {
			[contextProp]: PropTypes.object
		};

		observedBits!: number;

		// state 包含一个value属性
		state: ConsumerState<T> = {
			// 默认值为Provider提供的this.context[contextProp]对象
			value: this.getValue()
		};

		componentWillReceiveProps(nextProps: any) {
			// 通过observedBits可以控制当前子组件是否渲染
			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(): T {
			// 获取Provider提供的contextProp对象, contextProp存在,则调用get方法获取value,否则返回默认值defaultValue
			if (this.context[contextProp]) {
				return this.context[contextProp].get();
			} else {
				return defaultValue;
			}
		}

		onUpdate = (newValue: any, changedBits: number) => {
			// 取整
			const observedBits: number = this.observedBits | 0;
			if ((observedBits & changedBits) !== 0) {
				// 通过this.setState触发渲染
				this.setState({ value: this.getValue() });
			}
		};

		render() {
			// RenderFn默认取第一个子元素并传入当前的value对象
			return onlyChild(this.props.children)(this.state.value);
		}
	}

	return {
		Provider,
		Consumer
	};
}



复制代码

参考

用 globalThis 访问全局对象

以下参考与本文内容无关

React 源码讲解第 6 节- expirationTime 公式

React Time Slicing-ExpirationTime

文章分类
前端
文章标签