Skip to main content

render阶段总览

流程图-应用入口

基于18

入口流程-此时还不是fiber树,没有开启render阶段

<script type="text/babel">
const root = ReactDOM.createRoot(document.getElementById('root'))
root.render(<Test />);
</script>

createRoot-->ReactDOMRoot

function createRoot(container, options) {
return new ReactDOMRoot(root);
}

ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function (children) {
console.log('%c ==一切开始3:', 'color:red', 'ReactDOMRoot.prototype.render调用updateContainer()开启render阶段==', children);
var root = this._internalRoot;
}

17版本入口函数和18不一样

在18中直接调用以下函数,是不用走下面的流程的,18直接流程图的流程构建fiber和更新

ReactDOM.createRoot(domRoot).render(Ast)

以下是17或则在18中调用ReactDOM.render(ast,domRoot)的流程

17版本legacyRenderSubtreeIntoContainer()

根据 container 是否存在 root 区分初始化/更新,创建或获取 fiberRoot;

Babel 会把 JSX 转译成一个名为 React.createElement() 函数调用, 因此legacyRenderSubtreeIntoContainer 中打印的children实际上是通过React.createElement()处理过的

17中的React.createElement做的事情就是生成react的ast树 将参数读取转换为ast树的一些所需参数字段,最终返回ast树的结构

function createElement(type, config, children) {
//根据上面的示例代码,type=div,config= {className:'red'},children='Click Me'
var propName; // Reserved names are extracted
var props = {};// 我们常用的props 目前组件
var key = null;//该组件的唯一key
var ref = null;// 我们的ref
var self = null;
var source = null;

// ...
...
// ...

// 当发现arguments的参数大于1的时候。说明是有多个兄弟子元素的,如果等于1的话说明只有一个元素
var childrenLength = arguments.length - 2;

if (childrenLength === 1) {
// 直接将props的children设为当前children
props.children = children;
} else if (childrenLength > 1) {
var childArray = Array(childrenLength);

for (var i = 0; i < childrenLength; i++) {
childArray[i] = arguments[i + 2];
}

{
if (Object.freeze) {
Object.freeze(childArray);
}
}
// 有多个兄弟元素的话,将兄弟节点放置在一个数组里面,赋值给props的children
props.children = childArray;
} // Resolve default props

// ...
...
// ...

// ReactElement 返回回来的是我们最终的ast树的结构
return ReactElement(type, key, ref, self, source, ReactCurrentOwner.current, props);
}
// 从 container 取出 _reactRootContainer 作为 react 的一个根:
var maybeRoot = container._reactRootContainer;

检查 root 是否存在,如果存在就是 Update,如果不存在就是初始化。

  • 1.若root不存在:
    调用 legacyCreateRootFromDOMContainer初始化 root。 将 root 赋值给 container._reactRootContainer,取出 root 中的_internalRoot 作为 fiberRoot。

  • 2.若 root 存在, 从 root 中取出 fiberRoot:

root = maybeRoot;

调用 updateContainer(children,fiberRoot,parentComponent,callBack)

注意:这里 callBack 会通过 getPublicRootInstance() 递归找到 fiberRoot 上第一个非 HTMlElement 结点,并将 callback 绑定在它上边。

function render(element, container, callback) {
return legacyRenderSubtreeIntoContainer(null, element, container, false, callback);
}

/*
可以见到初始化阶段:legacyCreateRootFromDOMContainer 不批量调用 updateContainer

update阶段:直接调用 updateContainer
*/
function legacyRenderSubtreeIntoContainer(parentComponent, children, container, forceHydrate, callback) {
if (!maybeRoot) {
console.log('开始调用-->0-a0:初始化渲染')
// Initial mount
root = legacyCreateRootFromDOMContainer(container, children, parentComponent, callback, forceHydrate);
} else {
root = maybeRoot;

if (typeof callback === 'function') {
var originalCallback = callback;

callback = function () {
var instance = getPublicRootInstance(root);
originalCallback.call(instance);
};
} // Update

console.log('更新流程-->1-a1:即更新')
updateContainer(children, root, parentComponent, callback);
}

return getPublicRootInstance(root);
}

// 第一次渲染
legacyCreateRootFromDOMContainer(){

console.log('-->0-a,初始化渲染不执行批量更新,而是执行flushSync',)
flushSync(function () {
updateContainer(initialChildren, _root, parentComponent, callback);
});
}

React.createElement 旧api

https://cloud.tencent.com/developer/article/2135083

react17 之后我们可以不再依赖 React.createElement 这个 api 了,但是实际场景中以及很多开源包中可能会有很多通过 React.createElement 手动创建元素的场景:

React.createElement 其接收三个或以上参数:

  • type:要创建的 React 元素类型,可以是标签名称字符串,如 'div' 或者 'span' 等;也可以是 React组件 类型(class组件或者函数组件);或者是 React fragment 类型。
  • config:写在标签上的属性的集合,js 对象格式,若标签上未添加任何属性则为 null。
  • children:从第三个参数开始后的参数为当前创建的React元素的子节点,每个参数的类型,若是当前元素节点的 textContent 则为字符串类型;否则为新的 React.createElement 创建的元素。

17版本-createFiberRoot 创建fiberRoot对象

最终创建了 fiberRoot 和 rootFiber:

流程图构建fiber tree-接上面updateContainer

fiber 协调过程,构建fiber树的阶段可中断

注意: mountIndeterminateComponent()函数,code函数初始化在renderWithHooks这里执行

  • 调用 renderWithHooks 生成 value,value就是code()返回的树结构
  • 执行 reconcileChildren(null, workInProgress, value, renderLanes) 参数value

beginWork前置工作和beginWork进行中

  • performConcurrentWorkOnRoot(root, didTimeout)-->renderRootSync
    beginWork前置工作
function renderRootSync(root, lanes) {
// 省略
do {
try {
console.log('%c=render阶段准备:', 'color:red', 'renderRootSync()调用workLoopSync()-root:', { root });
workLoopSync();
break;
} catch (thrownValue) {
handleError(root, thrownValue);
}
} while (true);
// 省略
}

function workLoopSync() {
// Already timed out, so perform work without checking if we need to yield.
while (workInProgress !== null) {
performUnitOfWork(workInProgress);
}
}
  • performUnitOfWork-->beginWork$1开启beginWork
  • mountChildFibersreconcileChildFibers这两个方法的逻辑基本一致。唯一的区别是:reconcileChildFibers 会为生成的 Fiber 节点带上effectTag属性,而 mountChildFibers 不会。
function reconcileChildren(current, workInProgress, nextChildren, renderLanes) {
if (current === null) {
// If this is a fresh new component that hasn't been rendered yet, we
// won't update its child set by applying minimal side-effects. Instead,
// we will add them all to the child before it gets rendered. That means
// we can optimize this reconciliation pass by not tracking side-effects.
// 对于 mount 的组件
workInProgress.child = mountChildFibers(workInProgress, null, nextChildren, renderLanes);
} else {
// If the current child is the same as the work in progress, it means that
// we haven't yet started any work on these children. Therefore, we use
// the clone algorithm to create a copy of all the current children.
// If we had any progressed work already, that is invalid at this point so
// let's throw it out.
// 对于 update 的组件
workInProgress.child = reconcileChildFibers(workInProgress, current.child, nextChildren, renderLanes);
}
}

workInProgress构建步骤1

  • beginWork第一次会调用updateHostRoot进行初始化:updateHostRoot
  • 第二次才走 mountIndeterminateComponent 执行code()函数,此时的workInProgress.type 才有值;
  function beginWork(current, workInProgress, renderLanes) {
console.log('workInProgress', workInProgress, root)
debugger
workInProgress.lanes = NoLanes;
console.log('%c=beginWork()===start1-初始化', 'color:magenta', { getFiberName: getFiberName(workInProgress), current, renderLanes, workInProgress })
switch (workInProgress.tag) {
case IndeterminateComponent:
{
console.log('%c=beginWork()==end 2 mountIndeterminateComponent', 'color:magenta', workInProgress)
console.log(`%c=探究初始和hook=调用mountIndeterminateComponent`, 'color:blueviolet', workInProgress.type)
return mountIndeterminateComponent(current, workInProgress, workInProgress.type, renderLanes);
}
case HostRoot:
console.log('%c=beginWork()=end 6第一次会走这里初始化workInProgress', 'color:magenta')
console.log('%c=beginWork()=end 6 updateHostRoot', 'color:magenta')
return updateHostRoot(current, workInProgress, renderLanes);
}
}

render阶段completeUnitOfWork

render阶段结束,commit阶段前:此时fiber树生成

function performConcurrentWorkOnRoot(root, didTimeout) {
// 上面的render阶段流程
var exitStatus = shouldTimeSlice ? renderRootConcurrent(root, lanes) : renderRootSync(root, lanes);

// 省略
// 这里将会开启commit阶段的前置工作
finishConcurrentRender(root, exitStatus, lanes)
// 省略
}