概览
React-core
React 早期口号是 Rethinking Best Practices
(重新思考最佳实践)。背靠 Facebook 的 React,从开始起就不缺关注和用户,而且 React 想要做的是用更好的方式去颠覆前端开发方式(事实上跟早期 jquery 称霸前端,的确是颠覆了)。所以 React 推崇函数式编程(纯组件),数据不可变以及单向数据流。函数式编程最大的好处是其稳定性(无副作用)和可测试性(输入相同,输出一定相同),所以通常大家说的 React 适合大型应用,根本原因还是在于其函数式编程。
const React = {
Children: {
map,
forEach,
count,
toArray,
only,
},
createRef,
Component,
PureComponent,
createContext,
forwardRef,
lazy,
memo,
error,
warn,
useCallback,
useContext,
useEffect,
useImperativeHandle,
useDebugValue,
useLayoutEffect,
useMemo,
useReducer,
useRef,
useState,
Fragment: REACT_FRAGMENT_TYPE,
Profiler: REACT_PROFILER_TYPE,
StrictMode: REACT_STRICT_MODE_TYPE,
Suspense: REACT_SUSPENSE_TYPE,
createElement: __DEV__ ? createElementWithValidation : createElement,
cloneElement: __DEV__ ? cloneElementWithValidation : cloneElement,
createFactory: __DEV__ ? createFactoryWithValidation : createFactory,
isValidElement: isValidElement,
version: ReactVersion,
unstable_ConcurrentMode: REACT_CONCURRENT_MODE_TYPE,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: ReactSharedInternals,
};
export default React;
ReactElement.js
JSX 代码会被 Babel 编译为 React.createElement
,不引入 React 的话就不能使用 React.createElement
了。
function createElement(type, config, ...children) {
const props = {};
判断 {key, ref, __self, __source} = config,各自赋值给同名变量
然后 `hasOwnProperty.call(config, propName) && !RESERVED_PROPS.hasOwnProperty(propName)`剔除出去这四个内建属性,往props塞 ...config
判断 `childrenLength = arguments.length - 2` 是否1;
赋值 props.children `= childrenLe===1?children:Array(childrenLength)` 数组的话就for`childArray[i] = arguments[i + 2];`
判断 `type && type.defaultProps` 有的话 继续往 props塞 ...type.defaultProps
return ReactElement(
type,
key,
ref,
self,
source,
ReactCurrentOwner.current,
props,
);
}
const ReactElement = function (type, key, ref, self, source, owner, props) {
return {
$$typeof: REACT_ELEMENT_TYPE, // 通过 $$typeof 来帮助我们识别这是一个 ReactElement,作为一个flag: Symbol.for('react.element')
type: type, // 内建tag元素
key: key,
ref: ref,
props: props,
_owner: owner,
};
};
ReactBaseClasses.js
const emptyObject = {};
function Component(props, context, updater) {
this.props = props;
this.context = context;
this.refs = emptyObject; // refs 3种传入方式:1. string, 2. ref={el=>this.xxel = el}, 3. this.xxel = React.createRef();ref={this.xxel}
this.updater = updater || ReactNoopUpdateQueue;
// !Mark 重点: 下面两个重要原型方法都用到 updater 是 react-dom 中的内容
// ReactNoopUpdateQueue.js 中的内容,是为了保险起见,用来报警告 对应没有updater的情况下的各种方法【如enqueueSetState 会返回函数 warnNoop(publicInstance, 'setState')】
}
Component.prototype.isReactComponent = {};
Component.prototype.setState = function(partialState, callback) {
校验一下partialState是 object 或者 function 或者null
this.updater.enqueueSetState(this, partialState, callback, 'setState');
}
Component.prototype.forceUpdate = function(callback) {
this.updater.enqueueForceUpdate(this, callback, 'forceUpdate');
}
// 让 PureComponent 继承自 Component,使用了典型的寄生组合式。
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;
Object.assign(pureComponentPrototype, Component.prototype);
pureComponentPrototype.isPureReactComponent = true; // 相比Component 唯一增加的属性flag
ReactCreateRef.js
https://react.docschina.org/docs/refs-and-the-dom.html
function createRef() {
return {
current: null,
};
}
// 当 ref 属性用于 HTML 元素时,构造函数中使用 React.createRef() 创建的 ref 接收底层 DOM 元素作为其 current 属性。
// 当 ref 属性用于自定义 class 组件时,ref 对象接收组件的挂载实例作为其 current 属性。
// 你不能在函数组件上使用 ref 属性,因为他们没有实例。
forwardRef.js
function forwardRef<Props, ElementType: React$ElementType>(
render: (props: Props, ref: React$Ref<ElementType>) => React$Node,
) {
return {
$$typeof: REACT_FORWARD_REF_TYPE, // 又一个symbol flag: Symbol.for('react.forward_ref')
render,
};
}
这样使用(可以拿到 ref)
const FancyButton = React.forwardRef((props, ref) => (
<button ref={ref} className="FancyButton">
{props.children}
</button>
));
ReactChildren.js
export {
forEachChildren as forEach,
mapChildren as map,
countChildren as count,
onlyChild as only,
toArray,
};
重点看 mapChildren
// 使用 React.Children.map(this.props.children, c => [[c, c]])
function mapChildren(children, func, context) {
判断children==null就 直接返回children;
const result = [];
mapIntoWithKeyPrefixInternal(children, result, null, func, context);
return result;
}
function mapIntoWithKeyPrefixInternal(children, array, prefix, func, context) {
let escapedPrefix = '';
if (prefix != null) {
escapedPrefix = escapeUserProvidedKey(prefix) + '/';
}
// 引入了对象重用池的概念,维护一个大小固定(这里为10)的对象重用池,每次从这个池子里取一个对象去赋值,用完了就将对象上的属性置空然后丢回池子。维护这个池子的用意就是提高性能,毕竟频繁创建销毁一个有很多属性的对象会消耗性能
const traverseContext = getPooledTraverseContext(
array,
escapedPrefix,
func,
context,
);
traverseAllChildren(children, mapSingleChildIntoContext, traverseContext);
releaseTraverseContext(traverseContext);
}
function traverseAllChildren(children, callback, traverseContext) {
return children == null ? 0 :traverseAllChildrenImpl(children, '', callback, traverseContext);
}
// 把传入的 children 数组通过遍历摊平成单个节点,然后去执行 mapSingleChildIntoContext
// nameSoFar:Name of the key path so far.
// traverseContext:Used to pass information throughout the traversal
// callback:即 mapSingleChildIntoContext(bookKeeping, child, childKey) 函数
function traverseAllChildrenImpl(children, nameSoFar, callback, traverseContext) {
判断typeof children:
是undefined || boolean的话,
->赋值children成null -> 调用callback [并且return 1;给subtreeCount计数用]
string || number || object且children.$$typeof是Symbol.for('react.portal')的话,
->调用callback [并且return 1;给subtreeCount计数用]
// 上面两种情况即 传入的 child 是单个节点,callback 即 mapSingleChildIntoContext 执行这样
// callback(traverseContext, children, nameSoFar === '' ? '.' + getComponentKey(children, 0) : nameSoFar);
// getComponentKey(component, index)的逻辑是:如果有.key把component.key处理成('$'+(替换[=,:]成[=0,=2]的原key字符));如果没.key 返回index.toString(36);
剩下 object且children.$$typeof是Symbol.for('react.element') 这种情况继续往下执行
/** 上半部分只在children是单节点时执行,下半部分判断children是array或iterator*/
let child;
let nextName;
let subtreeCount = 0; // 存放 当前children层有的children数量
const nextNamePrefix = nameSoFar === '' ? "." : nameSoFar + ":";
if (Array.isArray(children)) {
// 节点是数组的话,就开始遍历数组,并且把数组中的每个元素再递归执行 traverseAllChildrenImpl
// 也用来摊平数组
child = children[i];
nextName = nextNamePrefix + getComponentKey(child, i);
subtreeCount += traverseAllChildrenImpl(
child,
nextName,
callback,
traverseContext,
);
} else { // 不是数组的话,就看看 children 是否可以支持迭代,???通过 obj[Symbol.iterator] 的方式去取,返回值如果是个函数的话就代表支持迭代,然后逻辑就和之前的一样了
const iteratorFn = getIteratorFn(children); // getIteratorFn内部逻辑:if(typeof (children[typeof Symbol === 'function' && Symbol.iterator] || children['@@iterator']) === 'function') return 他; 否则return null
if (typeof iteratorFn === 'function') {
const iterator = iteratorFn.call(children);
while (!(step = iterator.next()).done) {
同样的 执行traverseAllChildrenImpl(...)
}
}
else if(type === 'object') {invariant() children-type无效报警}
}
return subtreeCount; //给subtreeCount计数用
}
function mapSingleChildIntoContext(bookKeeping, child, childKey) { // bookKeeping就是我们从对象池子里取出来的东西
const {result, keyPrefix, func, context} = bookKeeping; // func 即Children.map(child,func)的arg[1]
let mappedChild = func.call(context, child, bookKeeping.count++);
if (Array.isArray(mappedChild)) {
// 对于这种情况的处理:React.Children.map(this.props.children, c => [c, c])
mapIntoWithKeyPrefixInternal(mappedChild, result, childKey, c => c); // 回到mapChildren(children, func, context)内的流程,往下会在traverseAllChildrenImpl内部 摊平mappedChild as Array
}
else if(mappedChild != null) { // 如果不是数组的话,
if (isValidElement(mappedChild)) { //判断返回值是否是一个有效的 Element
mappedChild = cloneAndReplaceKey( // 来自ReactElement.js `cloneAndReplaceKey(oldElement, newKey)`, clone 一份并且替换掉 key
mappedChild,
keyPrefix +
(mappedChild.key && (!child || child.key !== mappedChild.key)
? escapeUserProvidedKey(mappedChild.key) + '/' // escapeUserProvidedKey是把'/'替换成'$&/'
: '') +
childKey,
);
}
result.push(mappedChild);//最后把返回值放入 result 中,result 即 mapChildren 的返回值。
}
}
对象重用池相关逻辑
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) {
traverseContext.result = null;
traverseContext.keyPrefix = null;
traverseContext.func = null;
traverseContext.context = null;
traverseContext.count = 0;
if (traverseContextPool.length < POOL_SIZE) {
traverseContextPool.push(traverseContext);
}
}
渲染器
react-dom
将 React 组件渲染成 DOM。它实现了全局 ReactDOMAPI
实例
const APP = () => (
<div>
<span></span>
<span></span>
</div>
);
ReactDom.render(<APP />, document.querySelector("#root"));
在控制台document.querySelector('#root')._reactRootContainer
可以查看
react-dom/src/client/ReactDOM.js
export default const ReactDOM = {
createPortal,
finDOMNode(componentOrElement:Element | ?React$Component<any, any>) => null | Element | Text,
hydrate(element: React$Node, container: DOMContainer, callback?: Function),
render(element: React$Element<any>, container: DOMContainer, callback?: Function),
unstable_renderSubtreeIntoContainer(parentComponent: React$Component<any, any>, element: React$Element<any>, containerNode: DOMContainer, callback?: Function),
unmountComponentAtNode(container: DOMContainer),
unstable_createPortal(...args),
unstable_batchedUpdates:batchedUpdates,
unstable_interactiveUpdates: interactiveUpdates,
flushSync: flushSync,
unstable_createRoot: createRoot,
unstable_flushControlled: flushControlled,
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED: { Events: [
getInstanceFromNode,
getNodeFromInstance,
getFiberCurrentPropsFromNode,
EventPluginHubInjection.injectEventPluginsByName,
eventNameDispatchConfigs,
accumulateTwoPhaseDispatches,
accumulateDirectDispatches,
enqueueStateRestore,
restoreStateIfNeeded,
dispatchEvent,
runEventsInBatch,
flushPassiveEffects,
]}
}
render(element: React$Element<any>, container: DOMContainer, callback?: Function ) {
invariant(isValidContainer(container), 'Target container is not a DOM element.');
return legacyRenderSubtreeIntoContainer(
null,
element,
container,
false, // forceHydrate 参数,为 true 时是服务端渲染(hydrate 函数内会是true)
callback,
);
},
// 第一部分是没有 root 之前我们首先需要创建一个 root,第二部分是有 root 之后的渲染流程
function legacyRenderSubtreeIntoContainer(
parentComponent?: React$Component<any, any>,
children: ReactNodeList,
container: DOMContainer,
forceHydrate: boolean,
callback?: Function,
) {
let root = container._reactRootContainer;
if (!root) { // !mark: 上半部分
root = container._reactRootContainer = legacyCreateRootFromDOMContainer(
container,
forceHydrate,
);
if (typeof callback === 'function') { 处理一下传进来的callback 即 originalCallback: callback = ()=>{xxx; originalCallback.call(getPublicRootInstance(root._internalRoot))}}
unbatchedUpdates(() => { // 对于 Root 来说没必要批量更新,直接调用回调函数; 而其他 如 setState 会被优化batch成一次更新,减少了渲染次数。假定不会存在 parentComponent,因为很少有人会在 root 外部加上 context 组件。
如果parentComponent不存在,就
root.render(children, callback)
})
} else { // !mark: 下半部分
// 同样是 if (typeof callback === 'function') 那块的 callback逻辑 省略
// Update
if (parentComponent != null) {
root.legacy_renderSubtreeIntoContainer(
parentComponent,
children,
callback,
);
} else {
root.render(children, callback); // 直接执行 render
}
}
return getPublicRootInstance(root._internalRoot);
}
function legacyCreateRootFromDOMContainer(
container: DOMContainer,
forceHydrate: boolean,
): Root {
如果 不是 forceHydrate,就{
while ((rootSibling = container.lastChild)) {container.removeChild(rootSibling);}
const isConcurrent = false;
return new ReactRoot(container, isConcurrent, shouldHydrate);
}
}
function ReactRoot(
container: DOMContainer,
isConcurrent: boolean,
hydrate: boolean
) {
const root = createContainer(container, isConcurrent, hydrate); // 创建了一个 FiberRoot 对象,
this._internalRoot = root; // 并挂载到了 _internalRoot 上
// 和 DOM 树一样,fiber 也会构建出一个树结构(每个 DOM 节点一定对应着一个 fiber 对象),FiberRoot 就是整个 fiber 树的根节点,
}
ReactRoot.prototype.render = function (
children: ReactNodeList,
callback?: () => mixed
): Work {
const root = this._internalRoot;
const work = new ReactWork(); // function ReactWork() {this._callbacks = null;this._didCommit = false; this._onCommit = this._onCommit.bind(this);}
callback = callback === undefined ? null : callback;
if (callback !== null) {
work.then(callback); // ReactWork 的功能就是为了在组件渲染或更新后把所有传入ReactDom.render 中的回调函数全部执行一遍
}
updateContainer(children, root, null, work._onCommit); // work._onCommit 就是用于执行所有回调函数的
return work;
};
ReactRoot.prototype.unmount = function (callback?: () => mixed): Work {};
ReactRoot.prototype.legacy_renderSubtreeIntoContainer = function (
parentComponent: ?React$Component<any, any>,
children: ReactNodeList,
callback?: () => mixed
): Work {};
ReactRoot.prototype.createBatch = function (): Batch {};
react-reconciler/src/ReactFiberReconciler.js
export function createContainer(
containerInfo: Container,
isConcurrent: boolean,
hydrate: boolean,
): OpaqueRoot {
return createFiberRoot(containerInfo, isConcurrent, hydrate);
}
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent?: React$Component<any, any>,
callback?: Function,
): ExpirationTime {
const current = container.current; // container即fiberRoot(),fiberRoot的current 是 rootFiber Node
const currentTime = requestCurrentTime(); // 计算时间
const expirationTime = computeExpirationForFiber(currentTime, current); // expirationTime 代表优先级,数字越大优先级越高;同步的优先级最高 export const Sync = MAX_SIGNED_31_BIT_INT = 1073741823; 换算成天 约等于12天
return updateContainerAtExpirationTime(
element,
container,
parentComponent,
expirationTime,
callback,
);
}
export function updateContainerAtExpirationTime(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent?: React$Component<any, any>,
expirationTime: ExpirationTime,
callback?: Function,
) {
const current = container.current;
const context = getContextForSubtree(parentComponent); // // 获取 context 并赋值,这里肯定取不到值得,因为 ReactRoot.prototype.render里的 updateContainer(_,_,null,_)
if (container.context === null) {
container.context = context;
} else {
container.pendingContext = context;
}
return scheduleRootUpdate(current, element, expirationTime, callback);
}
// 最终计算出一个任务的执行截止时间。只要这个截止时间比 当前已经过的时间 大就可以一直让 React 延后这个任务的执行,以便让更高优先级的任务执行,但是一旦过了任务的截止时间,就必须让这个任务马上执行。
function scheduleRootUpdate(
current: Fiber,
element: ReactNodeList,
expirationTime: ExpirationTime,
callback?: Function,
) {
const update = createUpdate(expirationTime); // 一个固定几个keys的对象 工厂函数,创建个obj包括keys:expirationTime, tag, payload(setState 的第一二个参数), callback, next(用于在队列中找到下一个节点), nextEffect
update.payload = {element}; // render 的过程中其实也是一次更新的操作。对于render函数 是没有setState的所以也不可能有其第二个参数Payload,所以直接赋值 ReactRoot.prototype.render里的第一参数children
callback = callback === undefined ? null : callback;
if (callback !== null) {
catch下 callback-invalid的情况;
update.callback = callback;
}
flushPassiveEffects(); // import from ReactFiberScheduler.js, 与 useEffect 有关
enqueueUpdate(current, update); // import from ReactUpdateQueue.js 把 update 入队,内部就是一些创建或者获取 queue(链表结构),然后给链表添加一个节点的操作
scheduleWork(current, expirationTime); // import from ReactFiberScheduler.js, 调度相关
return expirationTime;
}
react-reconciler/src/ReactFiberScheduler.old.js
// react-reconciler/src/ReactFiberScheduler.js
export const requestCurrentTime = enableNewScheduler // import {enableNewScheduler} from 'shared/ReactFeatureFlags'; constant enableNewScheduler = false,所以我们只看 old 的代码
? requestCurrentTime_new
: requestCurrentTime_old; //import {requestCurrentTime as requestCurrentTime_old} from './ReactFiberScheduler.old.js'
function requestCurrentTime() {
if (isRendering) {
return currentSchedulerTime;
}
findHighestPriorityRoot();
if (
nextFlushedExpirationTime === NoWork ||
nextFlushedExpirationTime === Never
) {
recomputeCurrentRendererTime();
currentSchedulerTime = currentRendererTime;
return currentSchedulerTime;
}
return currentSchedulerTime;
}
function recomputeCurrentRendererTime() {
const currentTimeMs = now() - originalStartTimeMs; // 结果就是 现在离 React 应用初始化时经过了多少时间
// now()用的scheduler库,即[Performance.now()](https://developer.mozilla.org/zh-CN/docs/Web/API/Performance/now)
// originalStartTimeMs 是 React 应用初始化时就会生成的一个变量,值也是 performance.now()
currentRendererTime = msToExpirationTime(currentTimeMs); // => currentTimeMs / 10 | 0
}
function computeExpirationForFiber(currentTime: ExpirationTime, fiber: Fiber) {
let expirationTime;
// 各种if else 条件 省.. =>
if (expirationContext !== NoWork) {}
else if (isWorking) {}
else {
if (fiber.mode & ConcurrentMode) {
if (isBatchingInteractiveUpdates) expirationTime = computeInteractiveExpiration(currentTime) // 交互事件,优先级较高
else expirationTime = computeAsyncExpiration(currentTime) // 异步,优先级较低
if (nextRoot !== null && expirationTime === nextRenderExpirationTime) expirationTime -= 1; // do not update at the same expiration time that is already rendering
} else expirationTime = Sync // sync update 的情况
}
...
return expirationTime;
}
react-reconciler/src/ReactFiberExpirationTime.js
export function computeInteractiveExpiration(currentTime: ExpirationTime) {
return computeExpirationBucket(
currentTime,
HIGH_PRIORITY_EXPIRATION, // const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150;
HIGH_PRIORITY_BATCH_SIZE // const HIGH_PRIORITY_BATCH_SIZE = 100;
);
}
function computeExpirationBucket(
currentTime,
expirationInMs,
bucketSizeMs
): ExpirationTime {
return (
MAGIC_NUMBER_OFFSET - // const MAGIC_NUMBER_OFFSET = MAX_SIGNED_31_BIT_INT - 1;
ceiling(
MAGIC_NUMBER_OFFSET - currentTime + expirationInMs / UNIT_SIZE, // const UNIT_SIZE = 10;
bucketSizeMs / UNIT_SIZE // 为了抹平一段时间(这里=10)内的时间差,在抹平的时间差内不管有多少个任务需要执行,他们的过期时间都是同一个,这也算是一个性能优化,帮助渲染页面行为节流
)
);
}
function ceiling(num: number, precision: number): number {
return (((num / precision) | 0) + 1) * precision;
}
react-reconciler/src/ReactFiberRoot.js
// !mark: 在 createFiberRoot 函数内部,分别创建了两个 root,一个叫做 FiberRoot(FiberRootNode),另一个叫做 RootFiber(HostRootFiber),并且它们两者还是相互link的。
export function createFiberRoot(
containerInfo: any,
isConcurrent: boolean,
hydrate: boolean,
): FiberRoot {
const root: FiberRoot = new FiberRootNode(containerInfo, hydrate);
const uninitializedFiber = createHostRootFiber(isConcurrent);
root.current = uninitializedFiber;
uninitializedFiber.stateNode = root; // ,并且它们两者还是相互引用的
return root;
}
function FiberRootNode(containerInfo, hydrate) {// 内部创建了很多属性,需要了解两个属性,分别是 containerInfo 及 current。
this.current = null; // 指向 RootFiber。
this.containerInfo = containerInfo; //代表着容器信息,也就是我们的 document.querySelector('#root')
this.pendingChildren = null;
this.pingCache = null;
...
this.hydrate = hydrate;
this.firstBatch = null;
...
条件
this.memoizedInteractions = new Set();
this.pendingInteractionMap = new Map();
}
react-reconciler/src/ReactFiber.js
export function createHostRootFiber(isConcurrent: boolean): Fiber {
let mode = isConcurrent ? ConcurrentMode | StrictMode : NoContext;
if (enableProfilerTimer && isDevToolsPresent) {
mode |= ProfileMode;
}
return createFiber(HostRoot, null, null, mode);
}
const createFiber = function(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
): Fiber {
return new FiberNode(tag, pendingProps, key, mode);
};
function FiberNode(
tag: WorkTag,
pendingProps: mixed,
key: null | string,
mode: TypeOfMode,
) {
this.tag = tag;
this.key = key;
this.elementType = null;
this.type = null;
this.stateNode = null; // stateNode 保存了每个fiber节点的 DOM 信息
// Fiber
// return、child、sibling 这三个属性很重要,它们是构成 fiber 树的主体数据结构,return、child、sibling、index 组成了fiber单链表树结构
this.return = null; // 代表父 fiber
this.child = null; // child 代表子 fiber
this.sibling = null; // sibling 代表下一个兄弟节点,和链表中的 next 一个含义
this.index = 0; // 代表了当前 fiber 的索引
this.ref = null;
// 更新相关
this.pendingProps = pendingProps; // // 新的 props
this.memoizedProps = null; // // 旧的 props
this.updateQueue = null; // // 存储 setState 中的第一个参数
this.memoizedState = null; // // 旧的 state
this.contextDependencies = null;
this.mode = mode;
// Effects, 更新 DOM 相关
this.effectTag = NoEffect; // 指这个节点需要进行的 DOM 操作. import {NoEffect} from 'shared/ReactSideEffectTags'; 记录所有的 effect 的,记录是通过位运算来实现的:如新增`effectTag |= Update`,删除`effectTag &= ~Update`
this.nextEffect = null; // 下一个需要进行 DOM 操作的节点
this.firstEffect = null; // 第一个
this.lastEffect = null; // 最后一个
this.expirationTime = NoWork; // 任务过期时间
this.childExpirationTime = NoWork;
this.alternate = null; // 还有一个 alternate 属性很重要,这个属性代表了一个更新中的 fiber,大部分情况下每个 fiber 都有一个替身 fiber,在更新过程中,所有的操作都会在替身上完成,当渲染完成后,替身会代替本身
if (enableProfilerTimer) {...}
}
该文件下还有个export function createWorkInProgress(current: Fiber, pendingProps: any, expirationTime: ExpirationTime): Fiber {}
在一个 React 应用中,通常来说都有两个 fiebr 树,一个叫做 old tree,另一个叫做 workInProgress tree。前者对应着已经渲染好的 DOM 树,后者是正在执行更新中的 fiber tree,还能便于中断后恢复。两棵树的节点互相引用,便于共享一些内部的属性,减少内存的开销。毕竟前文说过每个组件或 DOM 都会对应着一个 fiber 对象,应用很大的话组成的 fiber 树也会很大,如果两棵树都是各自把一些相同的属性创建一遍的话,会损失不少的内存空间及性能。
当更新结束以后,workInProgress tree 会将 old tree 替换掉,这种做法称之为 double buffering,这也是性能优化里的一种做法
react-dom/src/events
react-native-renderer
实现了 React 和 React Native 的连接。
真正渲染 Native 视图的平台特定代码及组件都存储在 React Native 仓库中。
react-test-renderer
将 React 组件渲染为 JSON 树。这用于 Jest 的快照测试特性。
react-reconciler
“fiber” reconciler 是一个新尝试,致力于解决 stack reconciler 中固有的问题,同时解决一些历史遗留问题。Fiber 从 React 16 开始变成了默认的 reconciler。
流程大致如下:
ReactFiberScheduler.old.js
flushRoot(root: FiberRoot, expirationTime: ExpirationTime)
-> performWorkOnRoot(root, expirationTime, false) // root: FiberRoot, expirationTime: ExpirationTime, isYieldy: boolean,
-> renderRoot(root, isYieldy); // (root: FiberRoot, isYieldy: boolean): void // 这里开始渲染整颗树,这个函数在异步模式下可能会被多次执行,因为在异步模式下 // 可以打断任务。打断也就意味着每次都得回到 root 再开始从上往下循环
-> try { workLoop(isYieldy) } catch (thrownValue) {} // workLoop(isYieldy)
-> nextUnitOfWork = performUnitOfWork(nextUnitOfWork); // performUnitOfWork(workInProgress: Fiber): Fiber | null // 开始组件更新
-> next = beginWork(current, workInProgress, nextRenderExpirationTime); //
ReactFiberBeginWork.js
beginWork( current: Fiber | null, workInProgress: Fiber, renderExpirationTime: ExpirationTime ): Fiber | null {
switch(workInProgress.tag) {
// 21 种 case
// 几乎每种 case【case 对应列在 ~/shared/ReactWorkTags】 return的func 内 都最终执行reconcileChildren()。例:
case ClassComponent: {
// 省略几个const
return updateClassComponent( // updateClassComponent( current: Fiber | null, workInProgress: Fiber, Component: any, nextProps, renderExpirationTime: ExpirationTime )
current,
workInProgress,
Component,
resolvedProps,
renderExpirationTime,
);
}
}
}
-> finishClassComponent(
current,
workInProgress,
Component,
shouldUpdate,
hasContext,
renderExpirationTime
)
-> function finishClassComponent(
current: Fiber | null,
workInProgress: Fiber,
Component: any,
shouldUpdate: boolean,
hasContext: boolean,
renderExpirationTime: ExpirationTime,
) {
...
const instance = workInProgress.stateNode;
nextChildren = instance.render();
...
if (current !== null && didCaptureError) {}
else reconcileChildren(
current,
workInProgress,
nextChildren,
renderExpirationTime,
);
}
-> reconcileChildren( // 这里开始diff 算法,生成新的children
current: Fiber | null,
workInProgress: Fiber,
nextChildren: any,
renderExpirationTime: ExpirationTime
) {
if (current === null) {if (current === null) {
workInProgress.child = mountChildFibers( // 添加fiber
workInProgress,
null,
nextChildren,
renderExpirationTime,
);
}
else workInProgress.child = reconcileChildFibers( // maybe 复用fiber / delete / remove...
workInProgress,
current.child,
nextChildren,
renderExpirationTime,
);
}
ReactChildFiber.js
function reconcileChildFibers(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChild: any,
expirationTime: ExpirationTime,
): Fiber | null
-> ...省略string/number/isObject/iterator...
if (isArray(newChild)) return reconcileChildrenArray(
returnFiber,
currentFirstChild,
newChild,
expirationTime,
)
reconcileChildrenArray 简化流程
diff 相关。
fiberNode中的属性:
this.return = null; // 代表父 fiber
this.child = null; // child 代表子 fiber
this.sibling = null; // sibling 代表下一个兄弟节点,和链表中的 next 一个含义
this.index = 0; // 代表了当前 fiber 的索引
// 处理子节点是数组的情况
function reconcileChildrenArray(
returnFiber: Fiber,
currentFirstChild: Fiber | null,
newChildren: Array<*>,
expirationTime: ExpirationTime
): Fiber | null {
let resultingFirstChild: Fiber | null = null;
let previousNewFiber: Fiber | null = null;
let oldFiber = currentFirstChild; // 原父节点的第一个子节点
let lastPlacedIndex = 0;
let newIdx = 0; // 跟随 newChildren
let nextOldFiber = null;
// 第一轮 遍历,根据newIdx 同时对比 新旧二叉树。条件:存在原先的子节点 且 未遍历完需要更新的子节点
for (; oldFiber !== null && newIdx < newChildren.length; newIdx++) {
if (oldFiber.index > newIdx) {
nextOldFiber = oldFiber;
oldFiber = null;
} else nextOldFiber = oldFiber.sibling; // 正常来说 nextOldFiber 就是下一个节点了
const newFiber = updateSlot(
// 该函数目的:如果 key 相同的话就可以复用 oldFiber。(对应上面第一个if: oldFiber 如果为空的话,就会重新创建一个 fiber)
returnFiber,
oldFiber,
newChildren[newIdx],
expirationTime
);
}
}
虽然有三次 for 循环,但是本质上只是遍历了一次整个 newChild Array。
第一轮遍历的核心逻辑是复用和当前节点索引一致的老节点,一旦出现不能复用的情况就跳出 break current 遍历。
- 新旧节点都为文本节点,可以直接复用,因为文本节点不需要 key
- 其他类型节点一律通过判断 key 是否相同来复用或创建节点(可能类型不同但 key 相同)
当第一轮遍历结束后,其中存在两种特殊情况:
- newChild 已经遍历完: 当 newChild 已经遍历完 只需要把所有剩余的老节点都删除即可。删除的逻辑也就是(设置 effectTag 为 Deletion)
- 老的节点已经遍历完了:当出现老的节点已经遍历完了的情况时,就会开始第二轮遍历。这轮遍历的逻辑很简单,只需要把剩余新的节点全部创建完毕即可。
第三轮遍历的核心逻辑是找出可以复用的老节点并移动位置,不能复用的话就只能创建一个新的了。
首先把所有剩余的老节点都丢到一个 map 中。
在遍历的过程中会寻找新的节点的 key 是否存在于这个 map 中,
- 存在即可复用,如果复用成功,就应该把复用的 key 从 oldtree-map 中删掉,并且给复用的节点移动位置(给 effectTag 赋值为 Placement)
- 不存在就只能创建一个新的。
此轮遍历结束后,把还存在于 map 中的所有老节点删除