使用過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的流程圖