react中的fiber和初次渲染
源码中定义了不同类型节点的枚举值
组件类型
- 文本节点
- HTML标签节点
- 函数组件
- 类组件
- 等等
src/react/packages/react-reconciler/src/ReactWorkTags.js
export const FunctionComponent = 0;
export const ClassComponent = 1;
export const IndeterminateComponent = 2; // Before we know whether it is function or class
export const HostRoot = 3; // Root of a host tree. Could be nested inside another node.
export const HostPortal = 4; // A subtree. Could be an entry point to a different renderer.
export const HostComponent = 5;
export const HostText = 6;
export const Fragment = 7;
export const Mode = 8;
export const ContextConsumer = 9;
export const ContextProvider = 10;
export const ForwardRef = 11;
export const Profiler = 12;
export const SuspenseComponent = 13;
export const MemoComponent = 14;
export const SimpleMemoComponent = 15;
export const LazyComponent = 16;
export const IncompleteClassComponent = 17;
export const DehydratedFragment = 18;
export const SuspenseListComponent = 19;
export const ScopeComponent = 21;
export const OffscreenComponent = 22;
export const LegacyHiddenComponent = 23;
export const CacheComponent = 24;
什么是fiber
A Fiber is work on a Component that needs to be done or was done. There can be more than one per component.
fiber是指组件上将要完成或者已经完成的任务,每个组件可以一个或者多个。
fiber结构
为什么需要fiber
-
为什么需要fiber
对于大型项目,组件树会很大,这个时候递归遍历的成本就会很高,会造成主线程被持续占用,结果就是主线程上的布局、动画等周期性任务就无法立即得到处理,造成视觉上的卡顿,影响用户体验。
-
任务分解的意义
解决上面的问题
-
增量渲染(把渲染任务拆分成块,匀到多帧)
-
更新时能够暂停,终止,复用渲染任务
-
给不同类型的更新赋予优先级
-
并发方面新的基础能力
-
更流畅
创建fiber结构
fiber就是一个js对象来抽象vnode
function createFiber(vnode, returnFiber) {
const fiber = {
type: vnode.type,
key: vnode.key,
stateNode: null, // 原生标签时候指dom节点,类组件时候指的是实例
props: vnode.props,
child: null, // 第一个子fiber
sibling: null, // 下一个兄弟fiber
return: returnFiber, // 父节点
// 标记节点是什么类型的
flags: Placement,
deletions: null, // 要删除子节点 null或者[]
index: null, //当前层级下的下标,从0开始
// 记录上一次的状态 函数组件和类组件不一样
memorizedState: null,
// old fiber
alternate: null,
};
const { type } = vnode;
if (isStr(type)) {
// 原生标签
fiber.tag = HostComponent;
} else if (isFn(type)) {
// 函数组件或者是类组件
fiber.tag = type.prototype.isComponent ? ClassComponent : FunctionComponent;
} else if (isUndefined(type)) {
fiber.tag = HostText;
fiber.props = { children: vnode };
} else {
fiber.tag = Fragment;
}
return fiber;
}
深度优先遍历每个fiber
对不同的类型节点tag,都有对应的处理方法
function performUnitOfWork() {
const { tag } = wip;
switch (tag) {
// 原生标签 比如div span button p a
case HostComponent:
updateHostComponent(wip);
break;
case FunctionComponent:
updateFunctionComponent(wip);
break;
case ClassComponent:
updateClassComponent(wip);
break;
case Fragment:
updateFragmentComponent(wip);
break;
case HostText:
updateHostTextComponent(wip);
break;
default:
break;
}
if (wip.child) {
wip = wip.child;
return;
}
let next = wip;
while (next) {
if (next.sibling) {
wip = next.sibling;
return;
}
next = next.return;
}
wip = null;
}
初次渲染
在react项目中我们都是通过以下方法来初始化组件
ReactDOM.createRoot(document.getElementById("root")).render(jsx);
那我们就来实现一下该createRoot和render方法
源码中的render是挂载到了原型对象上
// react-dom
import createFiber from "./ReactFiber";
import { scheduleUpdateOnFiber } from "./ReactFiberWorkLoop";
// 构造函数
function ReactDOMRoot(internalRoot) {
this._internalRoot = internalRoot;
}
ReactDOMRoot.prototype.render = function (children) {
// 最原始的vnode节点(jsx) 我们需要的是fiber结构的vnode
const root = this._internalRoot;
// 原生dom节点
console.log(root, "root");
updateContainer(children, root);
};
// 初次渲染 组件到g根dom节点上
function updateContainer(element, container) {
const { containerInfo } = container;
const fiber = createFiber(element, {
type: containerInfo.nodeName.toLocaleLowerCase(),
stateNode: containerInfo,
});
// 组件初次渲染
scheduleUpdateOnFiber(fiber);
}
function createRoot(container) {
const root = { containerInfo: container };
return new ReactDOMRoot(root);
}
// 一整个文件是ReactDOM, createRoot是ReactDOM上的一个方法
export default { createRoot };
后面湖会继续补充react是如何完成后续的渲染流程的 scheduleUpdateOnFiber方法