export function createRoot( container: Element | Document | DocumentFragment, options?: CreateRootOptions, // 有两个参数可用,其余的属于非稳定值,不要使用 ): RootType { // 1. 检查container是否是DOM if (!isValidContainer(container)) { throw new Error('createRoot(...): Target container is not a DOM element.'); }
warnIfReactDOMContainerInDEV(container);
let isStrictMode = false; let concurrentUpdatesByDefaultOverride = false; let identifierPrefix = ''; let onRecoverableError = defaultOnRecoverableError; let transitionCallbacks = null;
exportfunctioncreateFiberRoot( containerInfo: Container, tag: RootTag, hydrate: boolean, initialChildren: ReactNodeList, hydrationCallbacks: null | SuspenseHydrationCallbacks, isStrictMode: boolean, concurrentUpdatesByDefaultOverride: null | boolean, // TODO: We have several of these arguments that are conceptually part of the // host config, but because they are passed in at runtime, we have to thread // them through the root constructor. Perhaps we should put them all into a // single type, like a DynamicHostConfig that is defined by the renderer. identifierPrefix: string, onRecoverableError: null | ((error: mixed) => void), transitionCallbacks: null | TransitionTracingCallbacks, formState: ReactFormState<any, any> | null, ): FiberRoot{ // 1. fiberRoot 表示Fiber数据结构对象, 是Fiber数据结构中的最外层对象。 const root: FiberRoot = (new FiberRootNode( containerInfo, tag, hydrate, identifierPrefix, onRecoverableError, formState, ): any); // 2. rootFiber表示组件挂载点对应的Fiber对象,比如React应用中默认的组件挂载点就是id为root的div。 const uninitializedFiber: Fiber = createHostRootFiber( tag, isStrictMode, concurrentUpdatesByDefaultOverride, ); // 3. 循环构造root 和 uninitializedFiber // fiberRoot包含rootFiber,在fiberRoot对象中有一个current属性,存储rootFiber。 root.current = uninitializedFiber; // rootFiber指向fiberRoot,在rootFiber对象中有一个stateNode属性,指向fiberRoot。 uninitializedFiber.stateNode = root;
if (enableCache) { // sy const initialCache = createCache(); retainCache(initialCache);
// The pooledCache is a fresh cache instance that is used temporarily // for newly mounted boundaries during a render. In general, the // pooledCache is always cleared from the root at the end of a render: // it is either released when render commits, or moved to an Offscreen // component if rendering suspends. Because the lifetime of the pooled // cache is distinct from the main memoizedState.cache, it must be // retained separately. root.pooledCache = initialCache; retainCache(initialCache); const initialState: RootState = { element: initialChildren, isDehydrated: hydrate, cache: initialCache, }; uninitializedFiber.memoizedState = initialState; } else { const initialState: RootState = { element: initialChildren, isDehydrated: hydrate, cache: (null: any), // not enabled yet }; uninitializedFiber.memoizedState = initialState; }
exportfunctioncreateHostRootFiber( tag: RootTag, isStrictMode: boolean, concurrentUpdatesByDefaultOverride: null | boolean, ): Fiber{ let mode; if (tag === ConcurrentRoot) { mode = ConcurrentMode; if (isStrictMode === true) { mode |= StrictLegacyMode | StrictEffectsMode; } if ( // We only use this flag for our repo tests to check both behaviors. forceConcurrentByDefaultForTesting ) { mode |= ConcurrentUpdatesByDefaultMode; } elseif ( // Only for internal experiments. allowConcurrentByDefault && concurrentUpdatesByDefaultOverride ) { mode |= ConcurrentUpdatesByDefaultMode; } } else { mode = NoMode; }
if (enableProfilerTimer && isDevToolsPresent) { // Always collect profile timings when DevTools are present. // This enables DevTools to start capturing timing at any point– // Without some nodes in the tree having empty base times. mode |= ProfileMode; }
// ! 2. 创建update const update = createUpdate(lane); // Caution: React DevTools currently depends on this property // being called "element". update.payload = {element};
exportfunctionrequestUpdateLane(fiber: Fiber): Lane{ // Special cases const mode = fiber.mode; // 1. 非ConcurrentMode模式 2. 目前不支持 if ((mode & ConcurrentMode) === NoMode) { return (SyncLane: Lane); } elseif ( (executionContext & RenderContext) !== NoContext && workInProgressRootRenderLanes !== NoLanes ) { // This is a render phase update. These are not officially supported. The // old behavior is to give this the same "thread" (lanes) as // whatever is currently rendering. So if you call `setState` on a component // that happens later in the same render, it will flush. Ideally, we want to // remove the special case and treat them as if they came from an // interleaved event. Regardless, this pattern is not officially supported. // This behavior is only a fallback. The flag only exists until we can roll // out the setState warning, since existing code might accidentally rely on // the current behavior. return pickArbitraryLane(workInProgressRootRenderLanes); }
// 普通更新与非紧急更新(18) const transition = requestCurrentTransition(); // 如果有transition if (transition !== null) { const actionScopeLane = peekEntangledActionLane(); return actionScopeLane !== NoLane ? // We're inside an async action scope. Reuse the same lane. actionScopeLane : // We may or may not be inside an async action scope. If we are, this // is the first update in that scope. Either way, we need to get a // fresh transition lane. requestTransitionLane(transition); }
// TODO: Move this type conversion to the event priority module. // React内部的一些update,比如flushSync,会通过上下文变量来跟踪其优先级 const updateLane: Lane = (getCurrentUpdatePriority(): any); if (updateLane !== NoLane) { // ? sy setState click 2 return updateLane; }
// TODO: Move this type conversion to the event priority module. // React外部的update,根据事件类型,向当前环境获取对应的优先级。 const eventLane: Lane = (getCurrentEventPriority(): any); return eventLane; }
// 类组件旧的生命周期相关的update if (isUnsafeClassRenderPhaseUpdate(fiber)) { // This is an unsafe render phase update. Add directly to the update // queue so we can process it immediately during the current render. const pending = sharedQueue.pending; if (pending === null) { // This is the first update. Create a circular list. update.next = update; } else { update.next = pending.next; pending.next = update; } sharedQueue.pending = update;
// Update the childLanes even though we're most likely already rendering // this fiber. This is for backwards compatibility in the case where you // update a different component during render phase than the one that is // currently renderings (a pattern that is accompanied by a warning). return unsafe_markUpdateLaneFromFiberToRoot(fiber, lane); } else { // sy return enqueueConcurrentClassUpdate(fiber, sharedQueue, update, lane); } }
functionenqueueUpdate( fiber: Fiber, queue: ConcurrentQueue | null, update: ConcurrentUpdate | null, lane: Lane, ) { // Don't update the `childLanes` on the return path yet. If we already in // the middle of rendering, wait until after it has completed. concurrentQueues[concurrentQueuesIndex++] = fiber; concurrentQueues[concurrentQueuesIndex++] = queue; concurrentQueues[concurrentQueuesIndex++] = update; concurrentQueues[concurrentQueuesIndex++] = lane;
// The fiber's `lane` field is used in some places to check if any work is // scheduled, to perform an eager bailout, so we need to update it immediately. // TODO: We should probably move this to the "shared" queue instead. fiber.lanes = mergeLanes(fiber.lanes, lane); const alternate = fiber.alternate; if (alternate !== null) { alternate.lanes = mergeLanes(alternate.lanes, lane); } }
// When a setState happens, we must ensure the root is scheduled. Because // update queues do not have a backpointer to the root, the only way to do // this currently is to walk up the return path. This used to not be a big // deal because we would have to walk up the return path to set // the `childLanes`, anyway, but now those two traversals happen at // different times. // TODO: Consider adding a `root` backpointer on the update queue. // __DEV__,检查是否有未挂载的Fiber,如Can't perform a React state update on a component that hasn't mounted yet. detectUpdateOnUnmountedFiber(sourceFiber, sourceFiber); let node = sourceFiber; let parent = node.return; // 循环往上查找,找到根节点 while (parent !== null) { detectUpdateOnUnmountedFiber(sourceFiber, node); node = parent; parent = node.return; }