alt=poster

写在前面

计划用半年的时间去深入 React 源码并记录下来,本文是系列文章第一章,前面大多数会以功能为主,不会涉及太多事务机制与流程,后半部分以架构、流程为主。这个是一个水到渠成的事情。看的越多,对其理解的广度就越大,深度也随之沉淀,在深入的同时站在作者的角度去思考,能够脱离源码照葫芦画瓢,才能算理解,读懂本身源码并不重要。可能没有什么休息时间,但是会尽量挤出来完善文章,也算是一种兴趣与习惯。

起源

2011 年,前端工程师 Jordan Walke 创建了ReactJS的早期原型FaxJS。

时间轴

从入口开始

时至今日(2019.9.28),五个小时前React已经将版本更新到 16.10.0 了,预计大半年内将步入17大版本。希望在系列文章完结之后更新(免得我又得看一遍)。

React 与 Vue 的源码相同的使用 Facebook 开源的 Flow 静态类型检查工具,为什么要用 Flow 而不用 Typescript ? 原因可能是 React 诞生的时间较早,那时候还没有 Typescript,后来也由于 Typescript 15年被社区广泛接受才火起来。还一个原因是 Flow 没有 Typescript 那么“严格”,所有的检查都是可选的。

fork/clone/open三部曲,找到 packages/react/src/React.js,剔除注释和空白行的源码还不到一百行,这个入口文件集成了所有的api暴露出去。

React中的源码与React-DOM分离,所以在packages/React内很多只是“形”上的API

import ReactVersion from '../../shared/ReactVersion';
import {
REACT_FRAGMENT_TYPE,
REACT_PROFILER_TYPE,
REACT_STRICT_MODE_TYPE,
REACT_SUSPENSE_TYPE,
REACT_SUSPENSE_LIST_TYPE,
} from '../../shared/ReactSymbols';

最顶部导入了React当前版本号,ReactSymbols 文件管理着全局的 React功能组件 Symbol 标志

Component

import {Component, PureComponent} from './ReactBaseClasses';

ComponentPureComponent 组件都是经常用的, 猜也能猜到都是定义一些初始化方法。

function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue; // 更新器
}

Component.prototype.isReactComponent = {};

Component.prototype.setState = function(partialState, callback) {
invariant(
typeof partialState === 'object' ||
typeof partialState === 'function' ||
partialState == null,
'setState(...): takes an object of state variables to update or a ' +
'function which returns an object of state variables.',
);
this.updater.enqueueSetState(this, partialState, callback, 'setState');// 加入更新队列
};

Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate'); // 强制加入更新队列
};

定义了Component类的 setStateforceUpdate 方法,以便在组件实例化后调用,将当前的props,context,refs进行绑定,并初始化更新。

每个组件内部都有一个 updater ,被用来驱动state更新的工具对象,执行更新队列,没传入updater时,this.updater 默认为 ReactNoopUpdateQueue,但是它没什么意义,只是做警告用的。

const ReactNoopUpdateQueue = {
isMounted: function(publicInstance) {
return false;
},
enqueueForceUpdate: function(publicInstance, callback, callerName) {
warnNoop(publicInstance, 'forceUpdate');
},
enqueueReplaceState: function(publicInstance, completeState, callback, callerName) {
warnNoop(publicInstance, 'replaceState');
},
enqueueSetState: function(publicInstance, partialState, callback, callerName) {
warnNoop(publicInstance, 'setState');
},
};

isMounted 在组件未挂载的情况下isMounted 一直会返回 false,例如在 constructor 里调用 setState或者组件已卸载/未使用,其他方法的作用是在开发环境下警告用户不要在constructor 内调用this原型上的方法。因为实际上真正的 updater 都是在 renderer 后注入的。真正的updater:

const classComponentUpdater = {
isMounted, // fn() => true
enqueueSetState(inst, payload, callback) {
// 获取fiber 也就是inst._reactInternalFiber
const fiber = getInstance(inst);

// 根据 msToExpirationTime(performance.now()) 得到一个时间,后续涉及到 ExpirationTime
const currentTime = requestCurrentTimeForUpdate();

// 获取suspense配置,与即将新增的 withSuspenseConfig Api强相关,默认情况下都是null
const suspenseConfig = requestCurrentSuspenseConfig();

// 根据开始实际计算任务过期时间
const expirationTime = computeExpirationForFiber(
currentTime,
fiber,
suspenseConfig,
);
//创建update对象
const update = createUpdate(expirationTime, suspenseConfig);

//setState的更新对象
update.payload = payload;

// setState的callback
if (callback !== undefined && callback !== null) {
if (__DEV__) {
warnOnInvalidCallback(callback, 'setState');
}
update.callback = callback;
}
// 放入队列
enqueueUpdate(fiber, update);

// 开始调度
scheduleWork(fiber, expirationTime);
},
// other ...
}

setState的任务调度以这种形式发出的,另外 与forceUpdate 、 replaceState 也差不多。

那上面提到的ExpirationTime是什么?

ExpirationTime

ExpirationTime是一个“保险”,为防止某个update因为优先级的原因一直被打断而未能执行。React会设置一个ExpirationTime,当时间到了ExpirationTime的时候,如果某个update还未执行的话,React将会强制执行该update,这就是ExpirationTime的作用。它有两种计算方法,一种computeInteractiveExpiration同步更新,与 computeAsyncExpiration 返回异步更新的expirationTime

//整型最大数值,V8中针对32位系统所设置的最大值 Math.pow(2,30) - 1;
export const Sync = MAX_SIGNED_31_BIT_INT; // 1073741823
export const Batched = Sync - 1; // 1073741822

const UNIT_SIZE = 10;
const MAGIC_NUMBER_OFFSET = Batched - 1; //1073741821

function msToExpirationTime(ms) {
return MAGIC_NUMBER_OFFSET - (ms / UNIT_SIZE | 0); // 1073741821 - now()/10|0
}

function ceiling(num: number, precision: number): number {
return (((num / precision) | 0) + 1) * precision; // 取整,本次区间分类最大值
}

// 计算过期时间
function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs): ExpirationTime {
/* LOW 任务 => 1073741821 - ceiling(1073741821 - currentTime + 5000 / 10, 250 / 10)
1073741821 - (((1073741821 - currentTime + 500) / 25) | 0) * 25 - 25
*/

/* HIGH任务 => 1073741821 - ceiling(1073741821 - currentTime + (__DEV__ ? 500 : 150) / 10, 100 / 10)
DEV 1073741821 - ceiling(1073741821 - currentTime + 50, 10)
1073741821 - (((1073741821 - currentTime + 50) / 10) | 0) * 10 - 10

!DEV 1073741821 - ceiling(1073741821 - currentTime + 15, 10)
1073741821 - (((1073741821 - currentTime + 15) / 10) | 0) * 10 - 10
*/
return (
MAGIC_NUMBER_OFFSET - ceiling(MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE)
);
}


// LOW 低优先级任务
export const LOW_PRIORITY_EXPIRATION = 5000;
export const LOW_PRIORITY_BATCH_SIZE = 250;
export function computeAsyncExpiration(currentTime: ExpirationTime): ExpirationTime {
return computeExpirationBucket(
currentTime,
LOW_PRIORITY_EXPIRATION,
LOW_PRIORITY_BATCH_SIZE,
);
}

// HIGH 高优先级任务
export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
export const HIGH_PRIORITY_BATCH_SIZE = 100;
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
return computeExpirationBucket(
currentTime,
HIGH_PRIORITY_EXPIRATION,
HIGH_PRIORITY_BATCH_SIZE,
);
}

export function computeSuspenseExpiration(
currentTime: ExpirationTime,
timeoutMs: number,
): ExpirationTime {
return computeExpirationBucket(
currentTime,
timeoutMs,
LOW_PRIORITY_BATCH_SIZE,
);
}

通过performance.now()生成 currentTime,当不支持performance时转利用Date.now(),这个值不用太过关注,只需要理解成时间戳即可。

const now1 = performance.now(); // 53028380
const now2 = performance.now(); // 53028389 ↑ 9
const now3 = performance.now(); // 53028391 ↑ 11
const now4 = performance.now(); // 53028405 ↑ 25
const now5 = performance.now(); // 53028420 ↑ 40
const now6 = performance.now(); // 53028430 ↑ 50
const now7 = performance.now(); // 53028444 ↑ 55
const now8 = performance.now(); // 53028468 ↑ 79

// LOW 任务
1073741821 - (((1073741821 - now1 + 500) / 25) | 0) * 25 - 25; // 53027871
1073741821 - (((1073741821 - now2 + 500) / 25) | 0) * 25 - 25; // 53027871 ↑ 0
1073741821 - (((1073741821 - now3 + 500) / 25) | 0) * 25 - 25; // 53027871 ↑ 0
1073741821 - (((1073741821 - now4 + 500) / 25) | 0) * 25 - 25; // 53027896 ↑ 25
1073741821 - (((1073741821 - now5 + 500) / 25) | 0) * 25 - 25; // 53027896 ↑ 25
1073741821 - (((1073741821 - now6 + 500) / 25) | 0) * 25 - 25; // 53027921 ↑ 50
1073741821 - (((1073741821 - now7 + 500) / 25) | 0) * 25 - 25; // 53027921 ↑ 50
1073741821 - (((1073741821 - now8 + 500) / 25) | 0) * 25 - 25; // 53027946 ↑ 75

// HIGH 任务 以DEV模式为例
1073741821 - (((1073741821 - now1 + 50) / 10) | 0) * 10 - 10; // 53028321
1073741821 - (((1073741821 - now2 + 50) / 10) | 0) * 10 - 10; // 53028331 ↑ 10
1073741821 - (((1073741821 - now3 + 50) / 10) | 0) * 10 - 10; // 53028331 ↑ 10
1073741821 - (((1073741821 - now4 + 50) / 10) | 0) * 10 - 10; // 53028351 ↑ 30
1073741821 - (((1073741821 - now5 + 50) / 10) | 0) * 10 - 10; // 53028361 ↑ 40
1073741821 - (((1073741821 - now6 + 50) / 10) | 0) * 10 - 10; // 53028371 ↑ 50
1073741821 - (((1073741821 - now7 + 50) / 10) | 0) * 10 - 10; // 53028391 ↑ 70
1073741821 - (((1073741821 - now8 + 50) / 10) | 0) * 10 - 10; // 53028411 ↑ 90

通过规律,可以看到LOW优先级任务时,区间<25的,得到的都是同一个值,而HIGH高优先级任务的区间为10,单位为毫秒,这个有什么用呢?

如果触发了多次事件,每次难道都要丢enqueueUpdate里立即调度?React让两个相近(25ms内)的update得到相同的expirationTime,它可以将多次事件分批打包丢入 enqueueUpdate里,假如我在24ms内触发了两个事件,那么React会将他们丢入同一批车,目的就是让这两个update自动合并成一个Update,并且只会触发一次更新,从而达到批量更新的目的。

从之前的代码看 computeInteractiveExpiration传入的是150、100,computeAsyncExpiration传入的是5000、250,前者的优先级更高,而过期执行时间为交互事件为 100/UNIT_SIZE = 10,异步事件则为 250/UNIT_SIZE = 25, 佐证了事实。

PureComponent

function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;

function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}

const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true;

Component 类相同属性的 PureComponent 类有所不同,首先创建了一个空函数 ComponentDummy,并将通过共享原型继承的方式将实例原型指向了 Component 的原型,其构造函数指定为PureComponent。其实就是在外面套了一层 pureComponentPrototypeComponent

createRef

import {createRef} from './ReactCreateRef';
export function createRef(): RefObject {
const refObject = {
current: null,
};
if (__DEV__) {
Object.seal(refObject);
}
return refObject;
}

返回一个refObject,其current的属性在组件挂载时进行关联,与react-dom强相关,后面再了解。现在只需要知道它很简单。

Children

import {forEach, map, count, toArray, only} from './ReactChildren';

React 将 children 的 API 暴露出来,这里最常使用的应该是 React.Children.mapReact.Children.forEach

Children.map

适用于替代 this.props.children.map ,因为这种写法通常用来嵌套组件,但是如果嵌套的是一个函数就会报错。而 React.Children.map 则不会。当需要写一个 Radio 组件需要依赖其父组件 RadioGroupprops 值,那么this.props.children.map 配合 cloneElement 简直不能再完美。还可以用来过滤某些组件。

React.cloneElement(props.children, {
name: props.name
})

render(){
return (
<div>
{
React.Children.map(children, (child, i) => {
if ( i < 1 ) return;
return child;
})
}
</div>
)
}

mapChildren

function mapChildren(children, func, context) {
if (children == null) {
return children;
}
const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
return result;
}

mapIntoWithKeyPrefixInternal

第一步,如果子组件为null直接不处理。正常情况下申明一个数组,进行加工。

function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
let escapedPrefix = '';
if (prefix != null) { // 顾名思义,处理key
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}

// 取出一个对象,作为上下文,遍历children
const traverseContext = getPooledTraverseContext(
array,
escapedPrefix,
func,
context,
);
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
// 释放对象
releaseTraverseContext(traverseContext);
}

getPooledTraverseContext 与 releaseTraverseContext

第二步,处理key的暂时不用管。最终通过 getPooledTraverseContext 到对象池里取一个对象,给 traverseAllChildren 进行处理,结束的时候通过 releaseTraverseContext reset所有属性放回去,做到复用,避免了一次性创建大量对象和释放对象消耗性能造成的内存抖动。

getPooledTraverseContext 用来取。
releaseTraverseContext 用来清空后放回

// 维护一个大小为 10 的对象重用池
const POOL_SIZE = 10;
const traverseContextPool = [];

function getPooledTraverseContext(
mapResult,
keyPrefix,
mapFunction,
mapContext,
) {
if (traverseContextPool.length) { // 如果当前对象池内有可用对象,就从队尾pop一个初始化后返回
const traverseContext = traverseContextPool.pop();
traverseContext.result = mapResult;
traverseContext.keyPrefix = keyPrefix;
traverseContext.func = mapFunction;
traverseContext.context = mapContext;
traverseContext.count = 0;
return traverseContext;
} else { // 否则返回一个新的对象
return {
result: mapResult,
keyPrefix: keyPrefix,
func: mapFunction,
context: mapContext,
count: 0,
};
}
}

function releaseTraverseContext(traverseContext) {
traverseContext.result = null;
traverseContext.keyPrefix = null;
traverseContext.func = null;
traverseContext.context = null;
traverseContext.count = 0;
if (traverseContextPool.length < POOL_SIZE) { // 如果对象池内没满,就放到对象池内,等待复用
traverseContextPool.push(traverseContext);
}
}

traverseAllChildren/traverseAllChildrenImpl

第三步,最重要的一步,在取出一个待复用对象后,traverseAllChildren 判断为null就没必要处理了。直接 return。

function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0; // return
}
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
function traverseAllChildrenImpl(
children, // children
nameSoFar, // 父级 key
callback, // 如果是可渲染节点
traverseContext, // 对象池复用对象
) {
const type = typeof children;

if (type === 'undefined' || type === 'boolean') {
// 以上都被认为是null。
children = null;
}
// 如果是可渲染的节点则为true,表示能调用callback
let invokeCallback = false;

if (children === null) {
invokeCallback = true;
} else {
switch (type) {
case 'string':
case 'number':
invokeCallback = true;
break;
case 'object':
switch (children.$$typeof) {
case REACT_ELEMENT_TYPE: // React元素 或者是 Portals
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}

if (invokeCallback) { // 可渲染节点,直接调用回调
callback( // 调用 mapSingleChildIntoContext
traverseContext,
children,
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
);
return 1;
}

let child;
let nextName;
let subtreeCount = 0; //在当前子树中找到的子级的层级数。
const nextNamePrefix = nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;

if (Array.isArray(children)) { // 如果children是数组,则递归处理
// 例如 React.Children.map(this.props.children, c => [[c, c]])
// c => [[c, c]] 会被摊平为 [c, c, c, c]
for (let i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i); // 在每一层不断用“:”分隔拼接key
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else { // 如果是对象的话通过 obj[Symbol.iterator] 取迭代器
const iteratorFn = getIteratorFn(children);
if (typeof iteratorFn === 'function') { // 如果是迭代器是函数就拿到结果
const iterator = iteratorFn.call(children);
let step;
let ii = 0;
while (!(step = iterator.next()).done) { // 继续递归处理
child = step.value;
nextName = nextNamePrefix + getComponentKey(child, ii++);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else if (type === 'object') { // 如果迭代器是普通对象也就无法迭代
let addendum = '';
const childrenString = '' + children;
invariant(
false,
'Objects are not valid as a React child (found: %s).%s',
childrenString === '[object Object]'
? 'object with keys {' + Object.keys(children).join(', ') + '}'
: childrenString,
addendum,
);
}
}

return subtreeCount;
}
const Demo = ({ children }) => {
console.log(React.Children.map(children, c => [[[[c, c]]]]));
return (
children
);
};
const Children = ({ msg }) => (
<span>
{ msg }
</span>
);

alt

上面函数的核心作用就是通过把传入的 children 数组通过遍历摊平成单个节点,其中迭代的所有callback都是 mapSingleChildIntoContext

mapSingleChildIntoContext

// bookKeeping getPooledTraverseContext 内从复用对象池取出来的 traverseContext
// child 传入的节点
// childKey 节点的 key
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
const {result, keyPrefix, func, context} = bookKeeping;

// func === React.Children.map(props.children, c => c) 的 c => c 函数
let mappedChild = func.call(context, child, bookKeeping.count++);
// 如果func返回值设定为数组 React.Children.map(this.props.children, c => [c, c])
// 表示每个元素将被返回两次。假如children为 c1,c2,那么最后返回的应该是c1,c1,c2,c2
if (Array.isArray(mappedChild)) {
// 是数组的话,就再调用 mapIntoWithKeyPrefixInternal
// 和 mapChildren 调用它的流程一样。递归将其铺平
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
} else if (mappedChild != null) {
// 如果是不为null并且是有效的 Element
if (isValidElement(mappedChild)) {
// 克隆 Element && 替换掉key 推入result
mappedChild = cloneAndReplaceKey(
mappedChild,
keyPrefix + (mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/' : '') + childKey,
);
}
result.push(mappedChild);
}
}

最终的逻辑又回到了 mapIntoWithKeyPrefixInternal ,通过递归调用使返回的数组结果展开铺平。

整体流程

大概整体流程

`mapChildren` ===================> `mapIntoWithKeyPrefixInternal` ==================> `getPooledTraverseContext`(复用对象池)
/\ ||
/||\ ||
|| ||
|| \||/
|| \/
||Yes `traverseAllChildren`(遍历children树)
|| ||
|| ||
|| \||/
|| \/
No || (children是数组又会重新递归执行)
`releaseTraverseContext`(释放对象池)<=====`mapSingleChildIntoContext`(铺平result)<=============`traverseAllChildrenImpl`

Children.forEach

相比 mapforEachChildren 则简单的多,因为不用去返回一个新的结果,只需要对children做遍历,

function forEachChildren(children, forEachFunc, forEachContext) {
if (children == null) {
return children;
}
const traverseContext = getPooledTraverseContext(
null, // 不需要返回数组,所以result为null
null, // key也不需要
forEachFunc,
forEachContext,
);
// 第二个参数 forEachSingleChild 简单调用了 forEachFunc
traverseAllChildren(children, forEachSingleChild, traverseContext);
releaseTraverseContext(traverseContext);
}

function forEachSingleChild(bookKeeping, child, name) {
const {func, context} = bookKeeping;
func.call(context, child, bookKeeping.count++);
}

Children.count

function countChildren(children) {
return traverseAllChildren(children, () => null, null);
}

用来计算children的个数,平时用的较少。和前面2个方法的行为差不多,预置的callback也不会进行任何处理。最终返回当前children的子元素,并不会向下递归查找。

Children.toArray

function toArray(children) {
const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, child => child);
return result;
}

用来将children转化成普通的数组,原理和 mapChildren 一样,可以用来将传入的children重新进行排序。

 class Sort extends React.Component {
render () {
const children = React.Children.toArray(this.props.children);
return <p>{children.sort((a,b)=>a-b).join('-')}</p>
}
}

<Sort>
{2}{5}{8}{4}{9}
</Sort>

// view
2-4-5-8-9

Children.only

function onlyChild(children) {
invariant(
isValidElement(children),
'React.Children.only expected to receive a single React element child.',
);
return children;
}

校验children是否为ReactElement,是则返回,否则报错。可以用来制做一个只接受一个 children<Single> 组件。

class Single extends Component{
render(){
return React.Children.only(this.props.children)
}
}

function App(){
return (
<Single>
<div>first</div>
<div>second</div> {/* error */}
</Single>
)
}

ReactElement

import {
createElement,
createFactory,
cloneElement,
isValidElement,
jsx,
} from './ReactElement';
const React = {
// ...
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement
}

开发环境下会自动validator,切到packages/react/src/ReactElement.js,大纲如下

hasValidRef ------------------------------- 判断是否有合理的ref
hasValidKey ------------------------------- 判断是否有合理的key
defineRefPropWarningGetter ---------------- 锁定props.ref
defineKeyPropWarningGetter ---------------- 锁定props.key
ReactElement ------------------------------ 转化ReactElement
jsx --------------------------------------- 使用jsx方式创建Element
jsxDEV ------------------------------------ 使用jsx方式创建Element(DEV)
createElement ----------------------------- 创建并返回指定类型的ReactElement
createFactory ----------------------------- 工厂模式createElement构造器
cloneAndReplaceKey ------------------------ 替换新key
cloneElement ------------------------------ 克隆Element
isValidElement ---------------------------- 判定是否ReactElement

createElement

这个方法大家耳熟能详。React用的最多的方法,没有之一。它负责React内所有元素节点的创建及初始化。

该方法接受三个参数 type, config, children ,type 是标签或者组件的名称,div/span/ul,对应的自定义Component首字母一定是大写。config 则包含所有的属性配置,children 代表子节点。

export function createElement(type, config, children) {
let propName;

const props = {};

let key = null;
let ref = null;
let self = null;
let source = null;

if (config != null) {
if (hasValidRef(config)) {// 查找config内是否存在合理的ref
ref = config.ref;
}
if (hasValidKey(config)) {// 查找config内是否存在合理的key
key = '' + config.key;
}

self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// 将剩余属性添加到新的props对象中
// 这也就是为什么<Component key={Math.random()}/>子组件中为什么找不到key/ref/__self/__source属性的原因
for (propName in config) {
if (
// 忽略原型链上的属性,并且抽离key/ref/__self/__source属性
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}

// children参数可以不止一个,除去前两个参数,其他的都是children
const childrenLength = arguments.length - 2;

// 对children做格式处理。一个为对象,多个则为Array
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
if (__DEV__) {
if (Object.freeze) {
Object.freeze(childArray);
}
}
props.children = childArray;
}

// 解析defaultProps,定义组件的默认Props属性
// Com.defaultProps = { msg:'default' }
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}
// 在开发环境下,锁定props上 key与 ref 的getter,不予获取
if (__DEV__) {
if (key || ref) {
const displayName = typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}
}
// 最后转化成React元素
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current, // 创建React元素的组件
props,
);
}
const ReactCurrentOwner = {
/**
* @internal
* @type {ReactComponent}
*/
current: (null: null | Fiber),
};

createElement 仅仅起到为ReactElement加工前过滤属性的作用。

create ReactElement

const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// $$typeof将其标识为一个React元素
$$typeof: REACT_ELEMENT_TYPE,

// ReactElement的内置属性
type: type,
key: key,
ref: ref,
props: props,

// 创建此元素的组件。
_owner: owner,
};
//为了利于测试,在开发环境下忽略这些属性(不可枚举),并且冻结props与element
if (__DEV__) {
element._store = {};
Object.defineProperty(element._store, 'validated', {
configurable: false,
enumerable: false,
writable: true,
value: false,
});
// self and source are DEV only properties.
Object.defineProperty(element, '_self', {
configurable: false,
enumerable: false,
writable: false,
value: self,
});
Object.defineProperty(element, '_source', {
configurable: false,
enumerable: false,
writable: false,
value: source,
});
if (Object.freeze) {
Object.freeze(element.props);
Object.freeze(element);
}
}

return element;
};

ReactElement 将其属性做二次处理,等待被渲染成DOM,在平常开发我们通过console.log打印出自定义Component 属性与element一致。

alt

createFactory

工厂模式的 createElement,通过预置 type 参数创建指定类型的节点。

var child1 = React.createElement('li', null, 'First Text Content');
var child2 = React.createElement('li', null, 'Second Text Content');
// 等价于
var factory = React.createFactory("li");
var child1 = factory(null, 'First Text Content');
var child2 = factory(null, 'Second Text Content');


var root = React.createElement('ul', {className: 'list'}, child1, child2);
ReactDOM.render(root,document.getElementById('root'));
export function createFactory(type) {
// 通过bind预置type参数返回新的函数
const factory = createElement.bind(null, type);
// Expose the type on the factory and the prototype so that it can be easily accessed on elements. E.g. `<Foo />.type === Foo`.
// This should not be named `constructor` since this may not be the function that created the element, and it may not even be a constructor.
// Legacy hook: remove it
factory.type = type;
return factory;
}

cloneElement

通过传入新的element与props及children,得到clone后的一个新元素。element 为cloneElement,config 是 newProps,可以重新定义 keyrefchildren 子节点。
整体方法与createElement大致相同。

export function cloneElement(element, config, children) {
// 判断有效的ReactElement
invariant(
!(element === null || element === undefined),
'React.cloneElement(...): The argument must be a React element, but you passed %s.',
element,
);

let propName;

// copy原始 props
const props = Object.assign({}, element.props);

// 提取保留key & ref
let key = element.key;
let ref = element.ref;
// 为了追踪与定位,继承被clone的Element这三个属性
const self = element._self;
const source = element._source;
let owner = element._owner;

if (config != null) {
// 这里的处理和createElement差不多
// unique ,ref 和 key 可以自定义覆盖
if (hasValidRef(config)) {
ref = config.ref;
owner = ReactCurrentOwner.current;
}
if (hasValidKey(config)) {
key = '' + config.key;
}

// 其他属性覆盖current props
let defaultProps;
if (element.type && element.type.defaultProps) {
defaultProps = element.type.defaultProps;
}
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
if (config[propName] === undefined && defaultProps !== undefined) {
// Resolve default props
props[propName] = defaultProps[propName];
} else {
props[propName] = config[propName];
}
}
}
}
// 剩余的与 createElement 一样
const childrenLength = arguments.length - 2;
if (childrenLength === 1) {
props.children = children;
} else if (childrenLength > 1) {
const childArray = Array(childrenLength);
for (let i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}
props.children = childArray;
}

return ReactElement(element.type, key, ref, self, source, owner, props);
}

cloneAndReplaceKey

顾名思义,与 cloneElement 名字上虽然差不多,但实际返回的是通过ReactElement传入newKey重新创建的旧Element。

export function cloneAndReplaceKey(oldElement, newKey) {
const newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement._self,
oldElement._source,
oldElement._owner,
oldElement.props,
);

return newElement;
}

isValidElement

也很简单,通过判断 $$typeof 是否为内置的ReactElement类型。

export function isValidElement(object) {
return (
typeof object === 'object' &&
object !== null &&
object.$$typeof === REACT_ELEMENT_TYPE
);
}

jsx/jsxDEV

没怎么使用过这个API,猜测应该是通过 react-dom 转化后使用的创建语法。

从代码逻辑上看,与 createElement 大致形同,jsxDEVjsx 多了两个能自定义的属性,sourceself,按照代码注释,是为了防止出现 <div key="Hi" {...props} /> 情况中 keyprops 先定义,导致被覆盖的情况。将对<div {...props} key="Hi" /> 之外的所有情况统一使用 jsxDEV 来强行赋值 key 与 ref。

export function jsxDEV(type, config, maybeKey, source, self) {
let propName;

// Reserved names are extracted
const props = {};

let key = null;
let ref = null;

// Currently, key can be spread in as a prop. This causes a potential issue if key is also explicitly declared (ie. <div {...props} key="Hi" /> or <div key="Hi" {...props} /> ). We want to deprecate key spread,
// but as an intermediary step, we will use jsxDEV for everything except <div {...props} key="Hi" />, because we aren't currently able to tell if key is explicitly declared to be undefined or not.
if (maybeKey !== undefined) {
key = '' + maybeKey;
}

if (hasValidKey(config)) {
key = '' + config.key;
}

if (hasValidRef(config)) {
ref = config.ref;
}

// Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}

// Resolve default props
if (type && type.defaultProps) {
const defaultProps = type.defaultProps;
for (propName in defaultProps) {
if (props[propName] === undefined) {
props[propName] = defaultProps[propName];
}
}
}

if (key || ref) {
const displayName =
typeof type === 'function'
? type.displayName || type.name || 'Unknown'
: type;
if (key) {
defineKeyPropWarningGetter(props, displayName);
}
if (ref) {
defineRefPropWarningGetter(props, displayName);
}
}

return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}

defineRefPropWarningGetter/defineKeyPropWarningGetter

key 是用来优化React渲染速度的,而 ref 是用来获取到React渲染后的真实DOM节点。正常情况下应该将这两个属性置之世外,仿佛这两个属性都应该是React本身的API。所以这两个方法就是用来禁止获取和设置的。

let specialPropKeyWarningShown, specialPropRefWarningShown;

function defineKeyPropWarningGetter(props, displayName) {
const warnAboutAccessingKey = function() {
if (!specialPropKeyWarningShown) { // 只会读取一次
specialPropKeyWarningShown = true;
warningWithoutStack(
false,
'%s: `key` is not a prop. Trying to access it will result ' +
'in `undefined` being returned. If you need to access the same ' +
'value within the child component, you should pass it as a different ' +
'prop. (https://fb.me/react-special-props)',
displayName,
);
}
};
warnAboutAccessingKey.isReactWarning = true;
Object.defineProperty(props, 'key', {
get: warnAboutAccessingKey,
configurable: true,
});
}

当在组件内尝试 console.log(props.key) 的时候,就会发现报错。

alt

两个方法逻辑一模一样,就不写粘贴两遍了。

hasValidRef/hasValidKey

这两个方法差不多,在开发模式下多了一个校验,通过 Object.prototype.hasOwnProperty 检查当前对象属性上是否存在 ref/key,并获取其访问器函数 get,如果事先被defineKeyPropWarningGetter/defineRefPropWarningGetter 锁定则 getter.isReactWarning 就必然为 true(注意锁定方法调用的时机)。

function hasValidRef(config) {
if (__DEV__) {
if (hasOwnProperty.call(config, 'ref')) {
const getter = Object.getOwnPropertyDescriptor(config, 'ref').get;
if (getter && getter.isReactWarning) {
return false;
}
}
}
return config.ref !== undefined;
}