使用过react开发的都知道,咱们直接写的
react element
会被babel转换为react.createElement
,具体以下图所示,那么先来看一下createElement的源码react
dom元素标签直接显示是字符串数组
而自定义组件显示的是变量 bash
export function createElement(type, config, children){}
复制代码
if (config != null) {
// key和ref单独处理,所以他们不会出如今props上
if (hasValidRef(config)) {
ref = config.ref;
}
if (hasValidKey(config)) {
key = '' + config.key;
}
// 赋值操做
self = config.__self === undefined ? null : config.__self;
source = config.__source === undefined ? null : config.__source;
// Remaining properties are added to a new props object
// 遍历配置,把内建的几个属性剔除后赋值到 props 中
for (propName in config) {
if (
hasOwnProperty.call(config, propName) &&
!RESERVED_PROPS.hasOwnProperty(propName)
) {
props[propName] = config[propName];
}
}
}
复制代码
若是子元素只有一个,直接props.children赋值,若是大于一个,用一个数组存储,而后赋值到props.childrenbabel
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(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
复制代码
const ReactElement = function(type, key, ref, self, source, owner, props) {
const element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE, // 元素标识
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner,
};
// ...
return element
}
复制代码
这里面主要是Component和pureComponent的实现app
函数Component有三个参数,props、context、updater(ReactDOM里面用于更新状态等的方法),props和context平时都是能够用到的,this.refs当咱们使用最先的字符串ref的时候能够这么获取到。dom
function Component(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
// We initialize the default updater but the real one gets injected by the
this.updater = updater || ReactNoopUpdateQueue;
}
复制代码
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.',
);
// 把state和callback回调函数 交给updater.enqueueSetState来处理
this.updater.enqueueSetState(this, partialState, callback, 'setState');
};
// 强制更新
/*
默认状况下,当组件的state或props改变时,组件将从新渲染。若是你的render()方法依赖于一些其余的数据,你能够告诉React组件须要经过调用forceUpdate()从新渲染。
调用forceUpdate()会致使组件跳过shouldComponentUpdate(),直接调用render()。这将触发组件的正常生命周期方法,包括每一个子组件的shouldComponentUpdate()方法。
forceUpdate就是从新render。有些变量不在state上,当时你又想达到这个变量更新的时候,刷新render;或者state里的某个变量层次太深,更新的时候没有自动触发render。这些时候均可以手动调用forceUpdate自动触发render
*/
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
};
复制代码
pureComponent是继承自Component,只不过多了一个用于区分的isPureReactComponent属性ide
// 几乎是和Component是一致的
function PureComponent(props, context, updater) {
this.props = props;
this.context = context;
// If a component has string refs, we will assign a different object later.
this.refs = emptyObject;
this.updater = updater || ReactNoopUpdateQueue;
}
// 这里是相似于Object.create方式的继承
function ComponentDummy() {}
ComponentDummy.prototype = Component.prototype;
const pureComponentPrototype = (PureComponent.prototype = new ComponentDummy());
pureComponentPrototype.constructor = PureComponent;
// 赋值prototype上的属性
// Avoid an extra prototype jump for these methods.
Object.assign(pureComponentPrototype, Component.prototype);
// 经过这个变量区别下普通的 Component
pureComponentPrototype.isPureReactComponent = true;
复制代码
refs的三种建立方式:函数
返回一个对象,经过对象的current来获取refoop
import type {RefObject} from 'shared/ReactTypes';
// an immutable object with a single mutable value
export function createRef(): RefObject {
const refObject = {
current: null,
};
if (__DEV__) {
Object.seal(refObject); // 封闭一个对象,阻止添加新属性并将全部现有属性标记为不可配置
}
return refObject;
}
复制代码
render函数多传了一个ref,注意这里的$$typeof又不一样了。ui
export default function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
return {
$$typeof: REACT_FORWARD_REF_TYPE,
render,
};
复制代码
这部分的源码主要是针对React.Children的,而且里面有一个对象池的概念。主要看一下React.children.map的处理
function mapChildren(children, func, context) {
if (children == null) {
return children;
}
// 遍历出来的元素会存放到result中,最终返回
const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
return result;
}
复制代码
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
// 处理 key
let escapedPrefix = '';
if (prefix != null) {
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
// getPooledTraverseContext 和 releaseTraverseContext 是配套的函数
// 用处其实很简单,就是维护一个大小为 10 的对象重用池
// 每次从这个池子里取一个对象去赋值,用完了就将对象上的属性置空而后丢回池子
const traverseContext = getPooledTraverseContext(
array,
escapedPrefix,
func,
context,
);
// 遍历全部children节点
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
// 释放对象池
releaseTraverseContext(traverseContext);
}
复制代码
const POOL_SIZE = 10;
const traverseContextPool = [];
function getPooledTraverseContext(
mapResult,
keyPrefix,
mapFunction,
mapContext,
) {
if (traverseContextPool.length) { // 数组中存在 直接取出来一个 赋值
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) { // 属性值置为null,而后放入到池子里面
traverseContext.result = null;
traverseContext.keyPrefix = null;
traverseContext.func = null;
traverseContext.context = null;
traverseContext.count = 0;
if (traverseContextPool.length < POOL_SIZE) {
traverseContextPool.push(traverseContext);
}
}
复制代码
callback为mapSingleChildIntoContext
function traverseAllChildren(children, callback, traverseContext) {
if (children == null) {
return 0;
}
return traverseAllChildrenImpl(children, '', callback, traverseContext);
}
复制代码
function traverseAllChildrenImpl(
children,
nameSoFar,
callback,
traverseContext,
) {
// 这个函数核心做用就是经过把传入的 children 数组经过遍历摊平成单个节点
// 而后去执行 mapSingleChildIntoContext
// 开始判断 children 的类型
const type = typeof children;
if (type === 'undefined' || type === 'boolean') {
// All of the above are perceived as null.
children = null;
}
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:
case REACT_PORTAL_TYPE:
invokeCallback = true;
}
}
}
// 若是 children 是能够渲染的节点的话,就直接调用 callback
// callback 是 mapSingleChildIntoContext
if (invokeCallback) {
callback(
traverseContext,
children,
// If it's the only child, treat the name as if it was wrapped in an array // so that it's consistent if the number of children grows.
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar,
);
return 1;
}
// nextName 和 nextNamePrefix 都是在处理 key 的命名
let child;
let nextName;
let subtreeCount = 0; // Count of children found in the current subtree.
const nextNamePrefix =
nameSoFar === '' ? SEPARATOR : nameSoFar + SUBSEPARATOR;
// 节点是数组的话,就开始遍历数组,而且把数组中的每一个元素再递归执行 traverseAllChildrenImpl
// 这一步操做也用来摊平数组的
// React.Children.map(this.props.children, c => [[c, c]])
// c => [[c, c]] 会被摊平为 [c, c, c, c]
if (Array.isArray(children)) {
for (let i = 0; i < children.length; i++) {
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
}
} else {
// 不是数组的话,就看看 children 是否能够支持迭代
// 就是经过 obj[Symbol.iterator] 的方式去取
const iteratorFn = getIteratorFn(children);
// 只有取出来对象是个函数类型才是正确的
if (typeof iteratorFn === 'function') {
// 而后就是执行迭代器,重复上面 if 中的逻辑了
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,
);
}
复制代码
function mapSingleChildIntoContext(bookKeeping, child, childKey) {
const {result, keyPrefix, func, context} = bookKeeping;
// func 就是咱们在 React.Children.map(this.props.children, c => c)
// 中传入的第二个函数参数
let mappedChild = func.call(context, child, bookKeeping.count++);
// 判断函数返回值是否为数组
// React.Children.map(this.props.children, c => [c, c])
// 对于 c => [c, c] 这种状况来讲,每一个子元素都会被返回出去两次
// 也就是说假若有 2 个子元素 c1 c2,那么经过调用 React.Children.map(this.props.children, c => [c, c]) 后
// 返回的应该是 4 个子元素,c1 c1 c2 c2
if (Array.isArray(mappedChild)) {
// 是数组的话就回到最早调用的函数中
// 而后回到以前 traverseAllChildrenImpl 摊平数组的问题
// 假如 c => [[c, c]],当执行这个函数时,返回值应该是 [c, c]
// 而后 [c, c] 会被当成 children 传入
// traverseAllChildrenImpl 内部逻辑判断是数组又会从新递归执行
// 因此说即便你的函数是 c => [[[[c, c]]]]
// 最后也会被递归摊平到 [c, c, c, c]
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c);
} else if (mappedChild != null) {
// 不是数组且返回值不为空,判断返回值是否为有效的 Element
// 是的话就把这个元素 clone 一遍而且替换掉 key
if (isValidElement(mappedChild)) {
mappedChild = cloneAndReplaceKey(
mappedChild,
// Keep both the (mapped) and old keys if they differ, just as
// traverseAllChildren used to do for objects as children
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/'
: '') +
childKey,
);
}
result.push(mappedChild);
}
}
复制代码
替换新key,返回元素
export function cloneAndReplaceKey(oldElement, newKey) {
const newElement = ReactElement(
oldElement.type,
newKey,
oldElement.ref,
oldElement._self,
oldElement._source,
oldElement._owner,
oldElement.props,
);
return newElement;
}
复制代码
ReactChildren的流程图