「07」初探render阶段

169 阅读4分钟

本章主要来了解Fiber节点是如何被创建并构建Fiber树的,

render阶段

  • 开始于performSyncWorkOnRootperformConcurrentWorkOnRoot方法的调用
  • 取决于本次更新是同步更新还是异步更新
// performSyncWorkOnRoot会调用该方法
function workLoopSync(){
    while(workInProgress!==null){
        performUnitOfWork(workInProgress)
    }
}

// performConcurrentWorkOnRoot会调用该方法
function workLoopConcurrent(){
    while(workInProgress!==null&&!shouldYield()){
        performUnitOfWork(workInProgress)
    }
}

唯一的区别是shouldYield,如果当前浏览器帧没有剩余时间shouldYield会中止循环,直到浏览器有空闲时间后再继续遍历

workInProgress代表当前已创建的workInProgress fiber

performUnitOfWork方法会创建下一个Fiber节点并赋值给workInProgress,并将workInProgress与已创建的Fiber节点连接起来构成Fiber树

由于Fiber Reconciler是从Stack Reconciler重构而来,通过遍历的方式实现可中断的递归,所以performUnitOfWork的工作可以分为两部分:

阶段

首先从rootFiber开始向下深度优先遍历,为遍历到的每个Fiber节点调用beginWork方法

该方法会根据传入的Fiber节点创建子Fiber节点,并将这两个Fiber节点连接起来

当遍历到叶子节点(即没有子组件的组件)时,就会进入阶段

阶段

阶段会调用completeWork处理Fiber阶段

当某个Fiber节点执行完completeWork,如果其存在兄弟Fiber节点(即fiber.sibling!==null),会进入其兄弟Fiber阶段

如果不存在兄弟Fiber,会进入父级Fiber阶段

阶段会交错执行直到rootFiber,至此,render阶段的工作就结束了

例子

function App(){
    return(
        <div>
            i am
            <span>KaSong</span>
        </div>
    )
}
ReactDOM.render(<App />,document.getElementById('root'))

对应的Fiber树结构:

render阶段依次执行:

rootFiber beginWork
App Fiber beginWork
div Fiber beginWork
'i am' Fiber beginWork
'i am' Fiber completeWork
span Fiber beginWork
span Fiber completeWork
div Fiber completeWork
App Fiber completeWork
rootFiber completeWork

实现beginWork

beginWork的工作是传入当前Fiber节点,创建子Fiber节点

workInProgress:当前组件对应的Fiber节点

rootFiber以外,组件mount时,由于是首次渲染,是不存在当前组件对应的Fiber节点在上一次更新时的Fiber节点,即mountcurrent===null

组件update时,由于之前已经mount过,所以current!==null

所以可以通过current===null?来区分组件是处于mount还是update

基于此原因,beginWork的工作可以分为两部分:

  • update时:如果current存在,在满足一定条件时可以复用current节点,这样就能克隆current.child作为workInProgress.child,而不需要新建workInProgress.child
  • mount时:除fiberRootNode以外,current===null,会根据fiber.tag不同,创建不同类型的子Fiber节点

目前只实现mount时的beginWork

  • beginWork接收一个参数wip,即当前组件对应的Fiber节点
  • 根据Fiber上的tag匹配需要执行的更新方法
// 递归中的递阶段
export const beginWork = (wip: FiberNode) => {
	// 比较,返回子fiberNode
	switch (wip.tag) {
		case HostRoot:
			return updateHostRoot(wip);
		case HostComponent:
			return updateHostComponent(wip);
		case HostText:
			return null;
		default:
			if (__DEV__) {
				console.warn('beginWork未实现的类型');
			}
			break;
	}
	return null;
};

function updateHostRoot(wip: FiberNode) {
	const baseState = wip.memoizedState;
	const updateQueue = wip.updateQueue as UpdateQueue<Element>;
	const pending = updateQueue.shared.pending;
	updateQueue.shared.pending = null;
	const { memoizedState } = processUpdateQueue(baseState, pending);
	wip.memoizedState = memoizedState;

	const nextChildren = wip.memoizedState;
	reconcileChildren(wip, nextChildren);
	return wip.child;
}

function updateHostComponent(wip: FiberNode) {
	const nextProps = wip.pendingProps;
	const nextChildren = nextProps.children;
	reconcileChildren(wip, nextChildren);
	return wip.child;
}

对于常见的组件类型FunctionComponent/ClassComponent/HostComponent最终会进入reconcileChildren方法

reconcileChildren

  • 对于mount的组件,会创建新的子Fiber节点
  • 对于update的组件,会将当前组件与该组件在上次更新时对应的Fiber节点比较(Diff),将比较的结果生成新的Fiber节点
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
	const current = wip.alternate;

	if (current !== null) {
		// update
		wip.child = reconcileChildFibers(wip, current?.child, children);
	} else {
		// mount
		wip.child = mountChildFibers(wip, null, children);
	}
}

不论走mount还是update逻辑,最终都会生成新的Fiber节点并赋值给workInProgress.child,作为本次beginWork返回值,并作为下次performUnitOfWork执行时workInProgress的传参

实现completeWork

类似beginWorkcompleteWork也是针对不同fiber.tag调用不同的处理逻辑

import {
	appendInitialChild,
	createInstance,
	createTextInstance
} from 'hostConfig';
import { FiberNode } from './fiber';
import { NoFlags } from './fiberFlags';
import { HostRoot, HostText, HostComponent } from './workTags';

export const completeWork = (wip: FiberNode) => {
	// 递归中的归

	const newProps = wip.pendingProps;
	const current = wip.alternate;

	switch (wip.tag) {
		case HostComponent:
			if (current !== null && wip.stateNode) {
				// update
			} else {
				// 1. 构建DOM
				const instance = createInstance(wip.type, newProps);
				// 2. 将DOM插入到DOM树中
				appendAllChildren(instance, wip);
				wip.stateNode = instance;
			}
			bubbleProperties(wip);
			return null;
		case HostText:
			if (current !== null && wip.stateNode) {
				// update
			} else {
				// 1. 构建DOM
				const instance = createTextInstance(newProps.content);
				wip.stateNode = instance;
			}
			bubbleProperties(wip);
			return null;
		case HostRoot:
			bubbleProperties(wip);
			return null;

		default:
			if (__DEV__) {
				console.warn('未处理的completeWork情况', wip);
			}
			break;
	}
};

function appendAllChildren(parent: FiberNode, wip: FiberNode) {
	let node = wip.child;

	while (node !== null) {
		if (node.tag === HostComponent || node.tag === HostText) {
			appendInitialChild(parent, node?.stateNode);
		} else if (node.child !== null) {
			node.child.return = node;
			node = node.child;
			continue;
		}

		if (node === wip) {
			return;
		}

		while (node.sibling === null) {
			if (node.return === null || node.return === wip) {
				return;
			}
			node = node?.return;
		}
		node.sibling.return = node.return;
		node = node.sibling;
	}
}

function bubbleProperties(wip: FiberNode) {
	let subtreeFlags = NoFlags;
	let child = wip.child;

	while (child !== null) {
		subtreeFlags |= child.subtreeFlags;
		subtreeFlags |= child.flags;

		child.return = wip;
		child = child.sibling;
	}
	wip.subtreeFlags |= subtreeFlags;
}

mount时

mount时的主要逻辑包括三个:

  • Fiber节点生成对应的DOM节点
  • 将子孙DOM节点插入刚生成的DOM节点
  • update逻辑中的updateHostComponent类似的处理props的过程
const currentHostContext = getHostContext();
// 为fiber创建对应DOM节点
const instance = createInstance(
    type,
    newProps,
    rootContainerInstance,
    currentHostContext,
    workInProgress,
  );
// 将子孙DOM节点插入刚生成的DOM节点中
appendAllChildren(instance, workInProgress, false, false);
// DOM节点赋值给fiber.stateNode
workInProgress.stateNode = instance;

// 与update逻辑中的updateHostComponent类似的处理props的过程
if (
  finalizeInitialChildren(
    instance,
    type,
    newProps,
    rootContainerInstance,
    currentHostContext,
  )
) {
  markUpdate(workInProgress);
}

由于completeWork属于阶段调用的函数,每次调用appendAllChildren时都会将已生成的子孙DOM节点插入当前生成的DOM节点下,那么当rootFiber时,我们已经有一个构建好的离屏DOM树

所有代码:

github.com/ohlyf/oh-re…

github.com/ohlyf/oh-re…