初读react源码 - part1 首屏渲染

75 阅读6分钟

执行顺序

ReactDOM.createRoot(root).render(App);

createRoot 方法

export function createRoot(container: Container) {
	const root = createContainer(container);
	return {
		render(element: ReactElementType) {
			updateContainer(element, root);
		}
	};
}

进入createContainer

/**
 * 创建容器
 * @param container
 * @returns
 */
export function createContainer(container: Container) {
	const hostRootFiber = new FiberNode(HostRoot, {}, null);
	const root = new FiberRootNode(container, hostRootFiber);
	hostRootFiber.updateQueue = createUpdateQueue();
	return root;
}

进入 FiberRootNode

/**
 * FiberRootNode
 */
export class FiberRootNode {
	container: Container;
	current: FiberNode;
	finishedWork: FiberNode | null; // 最终更新完成以后的fiber树
	/**
	 * @param container  容器 div#root
	 * @param hostRootFiber React的根节点
	 */
	constructor(container: Container, hostRootFiber: FiberNode) {
		this.container = container;
		this.current = hostRootFiber;
		hostRootFiber.stateNode = this;
		this.finishedWork = null;
	}
}

接着创建了一个空的更新队列

 

最后返回root

形成一下的一个关联

我们接着调试,当创建好上面的一个FiberRootNode 与 hostRootFiber 链接关系后,就回去运行render方法

我们去调用自己的demo中可以看见,创建root后调用了render这个方法

import React from 'react';
import ReactDOM from 'react-dom';

const App = (
	<div>
		<span>hello react</span>
	</div>
);

const root = document.getElementById('root');
debugger;
ReactDOM.createRoot(root).render(App);

console.log(React);
console.log(App);
console.log(ReactDOM);

render里面渲染的是我们定义的App

const App = (
	<div>
		<span>hello react</span>
	</div>
);

进入render方法后可以看见的是他直接去了updateContainer这个方法,

updateContainer的参数是element ,element是我们传入的jsx这个函数,root参数就 div#root这个根元素

import {
	createContainer,
	updateContainer
} from 'react-reconciler/src/fiberReconciler';
import { ReactElementType } from 'shared/ReactTypes';
import { Container } from './hostConfig';

export function createRoot(container: Container) {
	const root = createContainer(container);
	return {
		render(element: ReactElementType) {
			updateContainer(element, root);
		}
	};
}

updateContainer 这个方法接着进入了 createUpdate 其实就是创建了一个update对象

/**
 * 更新容器
 * @param element
 * @param root
 * @returns
 */
export function updateContainer(
	element: ReactElementType | null,
	root: FiberRootNode
) {
	const hostRootFiber = root.current;

	// update对象就是接下来要更新的element对象
	const update = createUpdate<ReactElementType | null>(element);

	// 给update赋值
	enqueueUpdate(
		hostRootFiber.updateQueue as UpdateQueue<ReactElementType | null>,
		update
	);
	scheduleUpdateOnFiber(hostRootFiber);
	return element;
}

enqueueUpdate接受的就是 hostRootFiber.updateQueue 也就是在上文中所提到的创建了一个更新队列。

可以看到enqueueUpdate就是更新 update。 update 目前就是 我们的App函数组件,可能小伙伴已经忘了App 函数了。

const App = (
  <div>
    <span>hello react</span>
  </div>
);
/**
 * 向队列中添加更新 向updateQueue 里面增加一个update
 * @param updateQueue 要更新的队列
 * @param update 更新值
 */
export const enqueueUpdate = <State>(
  updateQueue: UpdateQueue<State>,
  update: Update<State>
) => {
  updateQueue.shared.pending = update;
};

那么目前的内存中的一个状态

接着就调试到了scheduleUpdateOnFiber这个方法

这个方法就是去调用markUpdateFromFiberToRoot方法 接受的fiberhostRootFiber 对象

/**
 * 在fiber上调度更新
 * @param fiber
 */
export function scheduleUpdateOnFiber(fiber: FiberNode) {
	// todo 调度功能
	const root = markUpdateFromFiberToRoot(fiber);
	renderRoot(root);
}

我们进入 markUpdateFromFiberToRoot 方法。

function markUpdateFromFiberToRoot(fiber: FiberNode) {
  let node = fiber;
  let parent = node.return;

  // 找到根节点
  while (parent !== null) {
    node = parent;
    parent = node.return;
  }
  if (node.tag === HostRoot) {
    return node.stateNode;
  }

  return null;
}

因为进入的fiber参数是 hostRootFiber 所以 parent变量是一个 null 如果 不是一个null 就会去遍历寻找他的顶层节点 也就是 fiberRootNode。这里的意思就是我们更新就是从顶层向下更新的。

然后开始renderRoot, 他接受的是FiberRootNode 这个顶层节点。

function renderRoot(root: FiberRootNode) {
	// 初始化
	prepareFreshStack(root);

	do {
		try {
			workLoop();
			break;
		} catch (e) {
			if (__DEV__) {
				console.error('workLoop发生错误', e);
			}
			wip = null;
		}
	} while (true);

	const finishedWork = root.current.alternate;
	root.finishedWork = finishedWork;
	// 这里已经生成了wip fiberNode树 树中带有 flags
	commitRoot(root);
}

接着就去了prepareFreshStack方法。他做的是就是一个初始化一个 work in progress 可以理解为复制了一份节点

wip这个变量是一个全局变量哦,相当于链表的一个指针,指向我们正在处理的位置。

/**
 * 创建一个新的fiberNode
 * @param current 当前工作的fiberNode
 * @param pendingProps 刚开始工作的props
 * @returns 返回一个新的fiberNode
 */
export const createWorkInProgress = (
	current: FiberNode,
	pendingProps: Props
): FiberNode => {
	let wip = current.alternate;
	if (wip == null) {
		// 首屏渲染时(mount)
		wip = new FiberNode(current.tag, pendingProps, current.key);
		wip.stateNode = current.stateNode;

		wip.alternate = current;
		current.alternate = wip;
	} else {
		// 非首屏渲染时(update)
		wip.pendingProps = pendingProps;
		wip.flags = NoFlags;
		wip.subtreeFlags = NoFlags;
	}
	wip.type = current.type;
	wip.updateQueue = current.updateQueue; // 共用同一个 updateQueue对象
	wip.child = current.child;
	wip.memoizedState = current.memoizedState;
	wip.memoizedProps = current.memoizedProps;
	return wip;
};

此时属于首屏渲染时 所以 wip现在是null

调试到这里可以理解为wip是复制了一份 hostRootFiber

目前的内存模型变成

然后把一些属性复制到wip上面去。

这里就是react的一个双缓存机制

随后进入workLoop方法,

这里的 do while 是确保 workLoop 成功的执行

而workLoop方法是去执行 preformUnitOfWork 方法

/**
 * render阶段的入口
 */
function workLoop() {
	while (wip !== null) {
		preformUnitOfWork(wip);
	}
}

preformUnitOfWork他接受的fiber 是 wip 并且目前的链表表头是在 hostRoot tag = 3


function preformUnitOfWork(fiber: FiberNode) {
  // 代表子节点 递 
	const next = beginWork(fiber);
	fiber.memoizedProps = fiber.pendingProps;

  // 遍历到底层节点
	if (next === null) {
    // 就开始归
		completeUnitOfWork(fiber);
	} else {
		wip = next;
	}
}

beginWork 方法就是去判断目前是那个节点,首屏渲染 wip.tag = 3 进入 updateHostRoot方法

export function beginWork(wip: FiberNode) {
	// 比较 返回子fiberNode
	switch (wip.tag) {
		case HostRoot:
			return updateHostRoot(wip);
		case HostComponent:
			return updateHostComponent(wip);
		case HostText:
			return null;
		case FunctionComponent:
			return updateFunctionComponent(wip);
		default:
			if (__DEV__) {
				console.warn('beginWork: 未知的tag类型');
			}
			break;
	}
	return null;
}

可以看到的是 updateHostRoot 中

baseState 参数是null 因为现在是初始化 这里这个 memoizedState 是没有的。

updateQueue 是拿出了 wip的updateQueue

pending 是 获得了 wip 中的App 这个element对象 也就是下面这个

const App = (
	<div>
		<span>hello react</span>
	</div>
);

然后我们进入 processUpdateQueue 里面,由于我们的App是一个 element 对象随意直接走的else的逻辑 ,可以看到返回的 result 返回的是一个 element 对象

推出这个函数,此时的内存模型如下:

接着我们进入 reconcileChildren 这个方法

reconcileChildren 这个方法就是构建 fiber树的过程 ,然后它接受的参数

wip 就是 work in progress

nextChildren 就是拿到了 wip.memoizedState的 也就是 APP element 对象

/**
 * 构建 fiber树的过程
 * @param wip workInProgress
 * @param children element对象
 */
function reconcileChildren(wip: FiberNode, children?: ReactElementType) {
	// 获取父节点的currentNode
	const current = wip.alternate;
	if (current !== null) {
		// update
		wip.child = reconcileChildFibers(wip, current?.child, children);
	} else {
		// mount
		wip.child = mountChildFibers(wip, null, children);
	}
}

current 就是 hostRootFiber 这个节点

为什么在初次渲染走的update逻辑呢?

可以看到页面的内存图是这样的

第一挂载的时候 wip 是复制的了一份 hostRootFiber 会有属性altertnate 属性 左边是旧的节点,右边是新的节点。所以说这类似一个新节点替换旧节点是一个更新过程

如果说 没有alternate属性 就是挂在

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

reconcileChildFibers和mountChildFibers的逻辑是一样的, 只是shoulJTrackEffects的参数不一样,返回了一个新的函数。

update接受的参数是

returnFiber: wip

currentFiber: current?.child 也就是 hostRootFiber.child 属性 目前是null

newChild: app的element

现在我们进入 reconcileChildFibers 函数 我们发现 他是调用的另一个函数 childReconciler

export const reconcileChildFibers = childReconciler(true);
export const mountChildFibers = childReconciler(false);

进入

/**
 * 用于处理子节点的协调器
 * @param shouldTrackSideEffects  是否追踪副作用
 */
function childReconciler(shouldTrackSideEffects: boolean) {
	function reconcileSingleElement(
		returnFiber: FiberNode,
		currentFiber: FiberNode | null,
		element: ReactElementType
	) {
		// 根据element创建fiber
		// 在这里给子节点挂在return 和 父fiber进行关联的
		const fiber = createFiberFromElement(element);
		fiber.return = returnFiber;
		// 返回的是新子元素创建的fiber对象
		return fiber;
	}
	function reconcileSingleTextNode(
		returnFiber: FiberNode,
		currentFiber: FiberNode | null,
		content: string | number
	) {
		const fiber = new FiberNode(HostText, { content }, null);
		fiber.return = returnFiber;
		return fiber;
	}

	/**
	 * 插入单一的节点
	 * @param fiber
	 */
	function placeSingleChild(fiber: FiberNode) {
		// fiber是wip的fiber  这是一个首屏渲染的过程
		// 首屏渲染且追踪副作用时,才添加更新 flags
		if (shouldTrackSideEffects && fiber.alternate === null) {
			// TODO
			fiber.flags |= Placement;
		}
		return fiber;
	}

	return function reconcileChildFibers(
		returnFiber: FiberNode,
		currentFiber: FiberNode | null,
		newChild?: ReactElementType
	) {
		// 判断当前fiber的类型
		if (typeof newChild === 'object' && newChild !== null) {
			switch (newChild.$$typeof) {
				case REACT_ELEMENT_TYPE:
					return placeSingleChild(
						reconcileSingleElement(returnFiber, currentFiber, newChild)
					);
				default:
					if (__DEV__) {
						console.warn(
							'reconcileChildFibers: 未知的ReactElementType',
							newChild
						);
					}
					break;
			}
		}

		// TODO 多节点的情况 ul>li*3
		// 多个 Fragment 节点
		if (Array.isArray(newChild)) {
			// TODO: 暂时不处理
			if (__DEV__) {
				console.warn('未实现的 reconcile 类型', newChild);
			}
		}

		//  HOST_TEXT 文本节点的情况
		if (typeof newChild === 'string' || typeof newChild === 'number') {
			return placeSingleChild(
				reconcileSingleTextNode(returnFiber, currentFiber, newChild)
			);
		}

		if (__DEV__) {
			console.warn('reconcileChildFibers: 未知的ReactElementType', newChild);
		}

		return null;
	};
}

一进来会进入reconcileSingleElement这个函数,根据element创建fiber,然后给子节点挂在return 和 父fiber进行关联的最后返回的是新子元素创建的fiber对象。

最后placeSingleChild 返回的fiber如下

更新完成之后 就返回出去

然后 Root节点更新完成

第一轮beginWork执行完后的状态如下:

wip 与 div标签建立联系,并且为flags打上Placement标记