原生div p标签等的bieginWork
class DD extends Component {
render() {
return <div>123</div>;
}
}
上面调式了DD类组件的fiber,接着看类组件返回的子fiber,div标签的fiber是怎样的。
对于原生html的fiber,fiber.tag是5
export const HostComponent = 5; //原生标签
hostComponent的fiber
{
alternate: null
child: null
elementType: "div"
key: null
lanes: 16
memoizedState: null
mode: 1
pendingProps: {children: '123'}
ref: null
return: FiberNode {tag: 1, key: null, stateNode: DD, elementType: ƒ, type: ƒ, …}
sibling: null
stateNode: null
tag: 5
type: "div"
updateQueue: null
}
对于单一文本子节点的Fiber
,React
会特殊处理。比如div的子节点123,他没有beignWOrk和completeWork
在beginWOrk,对于HostComponent,会调用 updateHostComponent
方法
对于updateHostComponent,
function updateHostComponent(current, workInprogress, renderLanes){
const type = workInProgress.type; // div
const nextProps = workInProgress.pendingProps; //更新后的props
const prevProps = current !== null ? current.memoizedProps : null; //当前的Props
let nextChildren = nextProps.children; // <div>123</div>中的123 {children: 123}
//调用shouldSetTextContent优化
if (isDirectTextChild) {
nextChildren = null; // 设为Null,不创建子fiber
} else if (prevProps !== null && shouldSetTextContent(type, prevProps)) {
workInProgress.flags |= ContentReset;
}
//创建子fiber
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
他会调用shouldSetTextContent
判断是否为子节点创建fiber,他的判断是
function shouldSetTextContent(type,props){
return n type === 'textarea' || type === 'noscript' || typeof props.children === 'string' || typeof props.children === 'number'....
}
可以看到,对于像<div>123</div>
的123是不会创建子fiber的。
nextChildren置为了null,调用reconcileChildren
的时候,因为nextChildren === null,所以他不会创建子fiber,所以div Fiber返回的就是null。
那么就到了归阶段的逻辑了。
每个fiber的归阶段
我们知道performUnitOfWork
是每个fiber开始工作的函数。
function performUnitOfWork(unitOfWork: Fiber): void {
const current = unitOfWork.alternate; // 获取调度的fiber在当前页面上对应的fiber
let next;
...
// 调用beginWork,返回下一个工作节点
next = beginWork(current, unitOfWork, subtreeRenderLanes);
unitOfWork.memoizedProps = unitOfWork.pendingProps;
if (next === null) {
// 进入归阶段
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
如上,我们知道div Fiber在beginWork的reconcilerChildren的时候,因为优化,所以没有创建fiber,那么返回的就是null,所以next = null,那么就进入了归阶段。
我们的demo是这样的
const App: React.FC = () => {
return <DD />;
};
class DD extends Component {
render() {
return <div>123</div>;
}
}
我们了解了一共四种主要的fiber的beginWork阶段,hostRoot,也就是rootFiber,以及函数组件(mount的时候函数组件统统是IndeterminateComponent,只有当执行过后才会将tag改为FunctionComponent),以及类组件DD,和原生标签div等主要的tag的fiber的递阶段。
按照render阶段的递归逻辑。
beginWork执行阶段是
rootFiber beginWork
App beginWork
DD beginWork
div beginWork
completeWork的执行阶段则是
div beginWokr
DD beginWork
App beginWork
rootFiber beginWork
归阶段遵循一个原则。
-
当所有的子fiber完成归阶段的时候,就轮到自己完成归阶段了。
-
当自己完成归阶段的时候,就要判断有没有兄弟节点,有的话返回兄弟节点去执行beginWork,等兄弟节点执行完beginWork的时候,还会进来归阶段,从第一个原则继续开始判断。
-
当自己完成归阶段,并且没有兄弟节点,那么对于父节点来说,所有子fiber就完成了归阶段,那么就轮到父fiber执行completeWork。以此类推,直到rootFiber也完成completeWork,那么,render阶段到此结束
div fiber completeUnitOfWork
归阶段的入口是completeUnitOfWork
,来看下这个函数大概做了什么。
function completeUnitOfWork(unitOfWork){
let completedWork = unitOfWork;
// do while循环执行completeWork
do{
...
next = completeWork(current, completedWork, subtreeRenderLanes);
// 判断依据
const siblingFiber = completedWork.sibling;
if (siblingFiber !== null) {
// 如果有兄弟节点,就退出completWOrk,执行兄弟节点的beginWork
workInProgress = siblingFiber;
return;
}
//否则返回returnFiber,继续执行completeWork
completedWork = returnFiber;
workInProgress = completedWork;
}while(completedWork !== null)
}
如上,主要就是do while循环执行completeWork,有兄弟节点,就直接退出当前循环,执行兄弟节点的beginWOrk。没有就表示所有子fiber执行完毕,那么就轮到父fiber执行completeWork函数。
completeWork
对于completeWork,主要就是根据不同的tag执行不同的函数。按照顺序,第一个执行completeWOrk的就是div fiber。
对于div fiber,completeWork会走HostComponent。看看大概思路:
- 创建dom,挂载到fiber.stateNode
- 调用appendAllChildren,将自己子fiber的stateNode插入到创建的dom上
- 如果有ref,处理ref
// 获取挂载的element 即 <div id="root"/>的元素
const rootContainerInstance = getRootHostContainer();
if (current !== null && workInProgress.stateNode != null) {
// update阶段并且有dom元素
..}
else {
..
//创建Dom
const instance = createInstance(
type,
newProps,
rootContainerInstance,
currentHostContext,
workInProgress,
);
//关键函数,将自己儿子的dom插到自己身上
appendAllChildren(instance, workInProgress, false, false);
//将dom挂载在fiber.stateNode
workInProgress.stateNode = instance;
if (workInProgress.ref !== null) {
// 有ref元素
// If there is a ref on a host node we need to schedule a callback
markRef(workInProgress);
}
return null
}
- 对于createInstance,它会根据type创建对应的dom元素。
- 对于appendAllChildren,他会while循环,将子fiber的dom插入到当前dom上
- markRef会在fiber.tags上赋值一个标记。在commit阶段会处理
- 返回null,结束div fiber的归阶段。
类组件的completeWork
当div fiber结束completeWork的时候,
div fiber没有兄弟节点,所以returnFiber也就是类DDFiber继续while循环。
class DD extends Component {
render() {
return <div>123</div>;
}
}
对于类组件,
case ClassComponent: {
//类组件执行这个
const Component = workInProgress.type; // DD类
if (isLegacyContextProvider(Component)) {
popLegacyContext(workInProgress);
}
// 跟函数组件一样
bubbleProperties(workInProgress);
return null;
}
对于类组件,更新了儿子的优先级等等。
类组件执行完completeWork之后,因为DD Fiber没有兄弟节点,所以直接返回App Fiber也就是函数组件。由函数组件执行completeWork。而对于函数组件,
case IndeterminateComponent:
case LazyComponent:
case SimpleMemoComponent:
case FunctionComponent:
case ForwardRef:
case Fragment:
case Mode:
case Profiler:
case ContextConsumer:
case MemoComponent:
// 函数组件等执行这个
bubbleProperties(workInProgress);
return null;
执行的也是bubbleProperties这个函数,与类组件一样。
当App Fiber完成completeWork的时候。因为App Fiber没有兄弟节点,所以返回了RootFiber。
RootFiber的completeWork
updateHostContainer在首次执行的时候为空函数。bubbleProperites是跟类组件函数组件一样的执行。
当RootFiber执行完归阶段的时候
RootFiber没有兄弟节点,也没有父亲节点。所以跳出while循环。
当completeUnitOfWork执行完毕的时候
performUnitOfWork执行完毕。而performUnitOfWork是render阶段的入口,至此render阶段正式完成,workInProgress是null。
当下次Schedule执行performConcurrentWorkOnRoot的时候,最终调用workLoopConcurrent的时候,不用进循环了。
因为workInprogress为null,重置全局变量,然后返回一个退出状态。
renderRootConcurrent每次执行一次render阶段的函数之后,都会返回一个状态,用来给performConcurrentWorkOnRoot判断是否应该执行commit阶段。
状态一共有五个,推出时候的状态为RootCompleted,5表示已经完成。平常在执行的时候就是1,RootInProgress,表示在进行中。
然后看performConcurrentWorkOnRoot的逻辑、
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
将状态保存,然后会判断
if(exitStatus !== RootInProgress){
if(exitStatus === RootErrored){}....
else {
//exitStatus === RootCompleted的时候
const finishedWork: Fiber = (root.current.alternate: any); // root是FiberRoot,finishedWork就是已经完成了的workInprogress fiber树的rootFiber
root.finishedWork = finishedWork; // 挂载到FiberRoot上
//完成了render阶段之后,开启commit阶段
finishConcurrentRender(root, exitStatus, lanes);
}
}
最关键就是这个finishConcurrentRender函数,他会开启commit阶段。至此,Render阶段结束,准备开启Commit阶段。