处理之后的源码的地址 https://github.com/raotaohub/as-you-see-react ,直接拉下来就可以调试了,不用任何配置
调试源码之前需要对react的整个流程了解,对于原理也是需要了解的,可以推荐看下卡颂的react原理揭秘。看源码,必定是带着疑惑去的,解答自己的疑惑,看源码没必要钻牛角尖,忽略没必要的细节,例如某些没必要的参数,主要是搞清主流程
首先,需要对react的渲染流程需要有个大致的理解
渲染流程
React 整体的渲染流程就是 render(调度加调和) + commit(操作真实dom,执行effect回调)。可中断是在render阶段,commit是一并提交的
jsx的本质就是createElement,可以看下我前面关于react的文章。createElement执行产生了虚拟dom,虚拟dom在reconcile阶段与缓存的fiber树diff,产生新的fiber,其实也就是在旧fiber树上更改,这个阶段打上了标签,表示对应节点更改,删除,还是新增,这里有两种tag,一种表示的是节点本身的更新,另一种是subtreeTag,表示的是子节点是否更改(tag是作用在了commit阶段)。并且一般hooks也是在这个阶段处理的,例如useMemo,useCallBack的更新,但是对于useEffect,useEffectLayout的回调执行则是在commit阶段最开始就去处理了的。
初始的挂载流程(这里贴下源代码,我这里做了部分删减,保留了主流程)
从入口开始
ReactDom.createRoot(root).render(App)
创建根节点createRoot
方法
这个阶段就是跟我我们传入的root创建容器,给容器和hostRootFiber之间建立关联关系并且合成事件也是在这个阶段处理的
export function createRoot(
container: Element | Document | DocumentFragment,
options?: CreateRootOptions,
): RootType {
// 创建根节点,全局唯一
const root = createContainer(
container,
ConcurrentRoot,
null,
isStrictMode,
concurrentUpdatesByDefaultOverride,
identifierPrefix,
onRecoverableError,
transitionCallbacks,
);
// 标志为hoostRoot节点(后续不同类型节点会分流处理)
markContainerAsRoot(root.current, container);
const rootContainerElement: Document | Element | DocumentFragment =
container.nodeType === COMMENT_NODE
? (container.parentNode: any)
: container;
// 处理合成事件
listenToAllSupportedEvents(rootContainerElement);
// 绑定render方法
return new ReactDOMRoot(root);
}
ReactDOMHydrationRoot.prototype.render = ReactDOMRoot.prototype.render = function( children: ReactNodeList, ): void { const root = this._internalRoot; updateContainer(children, root, null, null); };
updateContainer
render中执行力了updateContainer,这里主要看下updateContainer中的enqueueUpdate
与scheduleUpdateOnFiber
export function updateContainer(
element: ReactNodeList,
container: OpaqueRoot,
parentComponent: ?React$Component<any, any>,
callback: ?Function,
): Lane {
// current指代了hostRootFiber
const current = container.current;
const eventTime = requestEventTime();
// 根据当前 Fiber
节点的情况,请求一个合适的更新优先级车道 const lane = requestUpdateLane(current); // 创建一个更新对象,包含事件时间和更新优先级车道信息 const update = createUpdate(eventTime, lane); // 这里是为了赋值,element其实就是jsx转译后的createElement函数,并不是VDom, update.payload = {element}; // 将更新对象添加到当前 Fiber
节点的更新队列中并向上寻找根节点 const root = enqueueUpdate(current, update, lane); if (root !== null) { scheduleUpdateOnFiber(root, current, lane, eventTime); entangleTransitions(root, current, lane); } return lane; }
关注一下enqueueUpdate
export function enqueueUpdate<State>( fiber: Fiber, update: Update<State>, lane: Lane, ): FiberRoot | null { const updateQueue = fiber.updateQueue; if (updateQueue === null) { return null; } const sharedQueue: SharedQueue<State> = (updateQueue: any).shared; // 绑定之前创建的update到对应fiber节点的更新队列上 if (isUnsafeClassRenderPhaseUpdate(fiber)) { const pending = sharedQueue.pending; if (pending === null) { update.next = update; } else { update.next = pending.next; pending.next = update; } sharedQueue.pending = update; // 最终都调用了markUpdateLaneFromFiberToRoot(向上寻找根节点,也就是一直递归return属性, // 查看对应节点的类型是不是hostFiber)
return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane); } else { // 处理了另一种情况,递归调用了enqueueUpdate return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane); } }
其实这里是不是会有个疑惑呢,current不就是根容器的current吗,这不就是根节点吗,为什么还需要再寻找呢。
这里应该提到双缓存了,其实这里的current时候指的当前页面展现所映射的这颗fiber树,但是也就是我们render所传入的app对应的fiber节点,但是根节点只有一个,就是createRoot所创建的,所以current并不是根节点。
scheduleUpdateOnFiber
这里主要是调用ensureRootIsScheduled
,
export function scheduleUpdateOnFiber(
root: FiberRoot,
fiber: Fiber,
lane: Lane,
eventTime: number,
) {
// 更新pendingLanes,标记根节点有一个待处理的更新,记录更新的lane和事件时间
markRootUpdated(root, lane, eventTime);
// 这里的executionContext,RenderContext,workInProgressRoot都是创建的全局变量
// executionContext,RenderContext表示的是render阶段
if ( (executionContext & RenderContext) !== NoLanes && root === workInProgressRoot ) {
workInProgressRootRenderPhaseUpdatedLanes = mergeLanes(
workInProgressRootRenderPhaseUpdatedLanes,
lane,
);
} else {
// 如果根节点是正在进行渲染的根节点,将更新lane。若根节点处于延迟挂起状态,则标记根节点为挂起。
if (root === workInProgressRoot) {
if (
deferRenderPhaseUpdateToNextBatch ||
(executionContext & RenderContext) === NoContext
) {
workInProgressRootInterleavedUpdatedLanes = mergeLanes(
workInProgressRootInterleavedUpdatedLanes,
lane,·
);
}
if (workInProgressRootExitStatus === RootSuspendedWithDelay) {
markRootSuspended(root, workInProgressRootRenderLanes);
}
}
// 调度更新
ensureRootIsScheduled(root, eventTime);
// 同步更新进入
if (
lane === SyncLane &&
executionContext === NoContext &&
(fiber.mode & ConcurrentMode) === NoMode &&
!(__DEV__ && ReactCurrentActQueue.isBatchingLegacy)
) {
resetRenderTimer();
flushSyncCallbacksOnlyInLegacyMode();
}
}
}
ensureRootIsScheduled
主要关注下performConcurrentWorkOnRoot
,scheduleCallback
是将任务同步到任务队列中
function ensureRootIsScheduled(root: FiberRoot, currentTime: number) {
const existingCallbackNode = root.callbackNode;
// 标记了过期的任务,低优先级任务一直未得到执行,会被标记为过期任务
markStarvedLanesAsExpired(root, currentTime);
// 返回下一个任务的lane以及优先级
const nextLanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
// 表示当前没有需要处理的更新任务。此时若存在现有回调任务节点,则调用 cancelCallback 函数取消该任务,
// 并将 FiberRoot 的回调任务节点和优先级重置为 null 和 NoLane
if (nextLanes === NoLanes) {
// Special case: There's nothing to work on.
if (existingCallbackNode !== null) {
cancelCallback(existingCallbackNode);
}
root.callbackNode = null;
root.callbackPriority = NoLane;
return;
}
// 获取新的回调优先级.
const newCallbackPriority = getHighestPriorityLane(nextLanes);
const existingCallbackPriority = root.callbackPriority;
// 优先级一样,直接返回了,也就是重用了当前的任务
if ( existingCallbackPriority === newCallbackPriority & ) {
return
}
// 将当前的任务挂起
if (existingCallbackNode != null) {
cancelCallback(existingCallbackNode);
}
// 调度了新的任务
let newCallbackNode;
// 同步任务
if (newCallbackPriority === SyncLane) {
if (root.tag === LegacyRoot) {
scheduleLegacySyncCallback(performSyncWorkOnRoot.bind(null, root));
} else {
scheduleSyncCallback(performSyncWorkOnRoot.bind(null, root));
}
if (supportsMicrotasks) {
if (ReactCurrentActQueue.current !== null) {
ReactCurrentActQueue.current.push(flushSyncCallbacks);
} else {
scheduleMicrotask(() => {
if (
(executionContext & (RenderContext | CommitContext)) ===
NoContext
) {
flushSyncCallbacks();
}
});
}
} else {
scheduleCallback(ImmediateSchedulerPriority, flushSyncCallbacks);
}
newCallbackNode = null;
} else {
// 异步
let schedulerPriorityLevel;
switch (lanesToEventPriority(nextLanes)) {
case DiscreteEventPriority:
schedulerPriorityLevel = ImmediateSchedulerPriority;
break;
case ContinuousEventPriority:
schedulerPriorityLevel = UserBlockingSchedulerPriority;
break;
case DefaultEventPriority:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
case IdleEventPriority:
schedulerPriorityLevel = IdleSchedulerPriority;
break;
default:
schedulerPriorityLevel = NormalSchedulerPriority;
break;
}
newCallbackNode = scheduleCallback(
schedulerPriorityLevel,
performConcurrentWorkOnRoot.bind(null, root),
);
}
root.callbackPriority = newCallbackPriority;
root.callbackNode = newCallbackNode;
}
进入到performConcurrentWorkOnRoot
中
performConcurrentWorkOnRoot
function performConcurrentWorkOnRoot(root, didTimeout) {
currentEventTime = NoTimestamp;
currentEventTransitionLane = NoLanes;
const originalCallbackNode = root.callbackNode;
// 处理了所有的副作用,例如effect的回调(effect的回调与调度挂钩),
// 所以对effect的解释是下一轮执行时,一定会执行上一轮的副作用
const didFlushPassiveEffects = flushPassiveEffects();
let lanes = getNextLanes(
root,
root === workInProgressRoot ? workInProgressRootRenderLanes : NoLanes,
);
const shouldTimeSlice =
!includesBlockingLane(root, lanes) &&
!includesExpiredLane(root, lanes) &&
(disableSchedulerTimeoutInWorkLoop || !didTimeout);
// 同步Or异步(进行时间切片,以并发方式渲染)这里关注下异步执行
let exitStatus = shouldTimeSlice
? renderRootConcurrent(root, lanes)
: renderRootSync(root, lanes);
ensureRootIsScheduled(root, now());
if (root.callbackNode === originalCallbackNode) {
return performConcurrentWorkOnRoot.bind(null, root);
}
return null;
}
enderRootConcurrent
主要关注的是enderRootConcurrent,在这里也就进入了wookLoop,主要是初始化了workInProgress
即fiber树遍历的指针
function renderRootConcurrent(root: FiberRoot, lanes: Lanes) { // 初始化了遍历的指针即workInProgress(这是全局变量)调用createWorkInProgress初始化遍历的指针 // 这里会判断workInProgress是否初始化了,初始化了则将root.current的某些属性赋值,没有初始化就new了一个fiber // 然后绑定alternate(双缓存,指向了另一颗fiber对应的节点,可以看一下fiber的数据结构)
prepareFreshStack(root, lanes); // 这里其实只是保证workLoopConcurrent一定会被执行 do { try { workLoopConcurrent(); break; } catch (thrownValue) { handleThrow(root, thrownValue); } } while (true); }
workLoopConcurrent
就是单纯的递归,处理节点的逻辑在beginWork里,shouldYield
即时间片,5ms,这就是可打断了,也就没过5ms,就让出主线程,
所以这里的逻辑是每处理一个节点,就判断是否超过了当前的时间片。
这里的处理流程:先向下递,即在递的过程重创建对应的fiber节点,并且处理children(这个阶段也只是连接了sibling,但是sibling系节点的children还没处理)
然后child为空时调用completeUnitOfWork
开始归,返回父节点,判断有没有有没有兄弟节点,有的话开始另一轮递的环节,也就是继续向下创建fiber节点。。。一直这样处理,知道返回了根节点,也就完成了本次的渲染
function workLoopConcurrent() {
while (workInProgress !== null && !shouldYield()) {
performUnitOfWork(workInProgress);
}
}
function performUnitOfWork(unitOfWork: Fiber): void {
let next;
if (enableProfilerTimer && (unitOfWork.mode & ProfileMode) !== NoMode) {
startProfilerTimer(unitOfWork);
next = beginWork(current, unitOfWork, renderLanes);
stopProfilerTimerIfRunningAndRecordDelta(unitOfWork, true);
} else {
next = beginWork(current, unitOfWork, renderLanes);
}
unitOfWork.memoizedProps = unitOfWork.pendingProps;
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
ReactCurrentOwner.current = null;
}
beginwork 。fiber树逐步向下构建的,在对应的函数里面创建了fiber节点处理了。。。。然后返回的child
伪代码 拿到当遍历的哪个fiber节点,然后根据节点内心 export const beginWork = (wip: FiberNode) => { // 比较,返回子fiberNode switch (wip.tag) { case 根节点(hostRootFiber): return ()=>fiber.Child; case 函数组件: return ()=>fiber.Child; case 类组件: return null; case memo: return ()=>fiber.Child; case forwordRef: return ()=>fiber.Child; default: console.warn('beginWork未实现的类型'); break; } return null; };
这里就先看下函数组件如何处理的,其他组件大致处理的逻辑是差不多的
function updateFunctionComponent(
current,
workInProgress,
Component,
nextProps: any,
renderLanes,
) {
let context;
let nextChildren;
let hasId;
hasId = checkDidRenderIdHook();
nextChildren = renderWithHooks(
current,
workInProgress,
Component, // workInProgress.type 其实就是函数 并不是被转译的jsx
nextProps,
context,
renderLanes,
);
hasId = checkDidRenderIdHook();
workInProgress.flags |= PerformedWork;
reconcileChildren(current, workInProgress, nextChildren, renderLanes);
return workInProgress.child;
}
这里就关注下renderWithHooks
与reconcileChildren
reconcileChildren
是做调和的,也就是Vdom转fiber。 也就是diff阶段,这个阶段会做一个fiber节点的复用,也会打上tag(表明自身节点的新增,修改,删除),但是还有subTreeTag(当前节点下的子节点的tag和)是怎么标记的,是在归过程中计算的,即父节点的subTreeTag就等于对应的子节点的subTreeTag和。
还有一个问题,节点的其实只会做新增和删除,并不会去修改其属性,那么为什么有更新的tag呢?因为节点的顺序变了,
renderWithHooks
就是hooks 的实现
这里后面补一下,内容有点多。。。。记个TODO
这时假设已经到底了,即next=== null 跳回到workLoopConcurrent
if (next === null) {
// If this doesn't spawn new work, complete the current work.
completeUnitOfWork(unitOfWork);
} else {
workInProgress = next;
}
completeUnitOfWork
-
创建真实的dom节点,但是还没渲染到页面中
-
处理flags,合并位subTreeFlags(flags是自己节点有没有更改,subTreeFlags是表面自己节点下面的子节点是否有更改。作用在commit阶段)
-
建立真实dom的关系,把子dom插入到父dom中
就是由低向上归即在这个阶段创建的真实dom节点,绑定到了对应fiber节点的stateNode属性上,这里的部分比较复杂后续跟beginWork,将流程补齐。
commit阶段
进入commit阶段 即在performConcurrentWorkOnRoot中,根据exitStatus
的状态判断如何处理渲染的结果
performConcurrentWorkOnRoot
完成了直接提交
finishConcurrentRender(root, exitStatus, lanes);
完成状态直接commitRoot
function finishConcurrentRender(root, exitStatus, lanes) {
switch (exitStatus) {
case RootCompleted: {
commitRoot(
root,
workInProgressRootRecoverableErrors,
workInProgressTransitions,
);
break;
}
default: {
throw new Error('Unknown root exit status.');
}
}
}
commitRoot
处理副作用,即effect的回调函数这里会将回调函数投入到任务队列中去,是与调度挂钩的
根据subtreeTag判断节点是否有更改,是否有在BeforeMutationMask | MutationMask | LayoutMask | PassiveMask阶段需要执行的任务,如果说不存在的话直接将root节点的current指针指向缓存中的那颗fiber树即可,
function commitRootImpl( root: FiberRoot, recoverableErrors: null | Array<CapturedValue<mixed>>, transitions: Array<Transition> | null, renderPriorityLevel: EventPriority){ // 处理副作用 flushPassiveEffects() const subtreeHasEffects = (finishedWork.subtreeFlags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags; const rootHasEffect = (finishedWork.flags & (BeforeMutationMask | MutationMask | LayoutMask | PassiveMask)) !== NoFlags; if (subtreeHasEffects || rootHasEffect) { // before mutation commitBeforeMutationEffects( root, finishedWork, ); // mutation 根据fiber节点的tag和subTreeTag增删改 dom, commitMutationEffects(root, finishedWork, lanes); // layout 在这个阶段更新ref,同步的执行useLayoutEffect的回调等 commitLayoutEffects(finishedWork, root, lanes); }else { root.current = finishedWork; } }
记个TODO 补齐下react的合成事件原理,hooks实现原理,diff的过程,还有就是react的调度机制。
共有 0 条评论