You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
# install dependencies
yarn
# for vscode extension to discover .flowconfig
ln -sfr scripts/flow/custom/.flowconfig .flowconfig
# build for minimal demo page
yarn build react/index react-dom/index --type=UMD_DEV
xdg-open fixtures/packaging/babel-standalone/dev.html
# testing
yarn test packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js
# debugging (cf. https://jestjs.io/docs/troubleshooting#debugging-in-vs-code)# See below for how to enable source map. (this issue is also relevant https://github.com/facebook/react/issues/14361)
yarn test --debug packages/react-test-renderer/src/__tests__/ReactTestRendererAct-test.js # then hit F5
yarn test --debug packages/react-reconciler/src/__tests__/ReactEffectOrdering-test.js -t 'passive unmounts on deletion are fired'
act =>
initialize ReactCurrentActQueue.current to skip scheduler logic for testing
(callback) =>
React.createElement(App, ...) (instantiate `ReactElement` with `App` type)
ReactTestRenderer.create =>
createContainer (returns FiberRoot) =>
createFiberRoot =>
new FiberRootNode (create FiberRoot)
createHostRootFiber (createFiber with `HostRoot` tag)
initializeUpdateQueue
updateContainer(ReactNodeList, FiberRoot) =>
requestUpdateLane
createUpdate (`UpdateState` tag with `ReactNodeList` payload (here `<App />` element))
enqueueUpdate (insert `Update` to `Fiber.updateQueue` linked list)
scheduleUpdateOnFiber =>
ensureRootIsScheduled =>
scheduleLegacySyncCallback(performSyncWorkOnRoot)
scheduleCallback(..., flushSyncCallbacks) (scheduleMicrotask if available) =>
push to ReactCurrentActQueue
flushActQueue =>
for each callback in `ReactCurrentActQueue.current`
(e.g.) flushSyncCallbacks =>
for each callback in `syncQueue` =>
(e.g.) performSyncWorkOnRoot => ...
performSyncWorkOnRoot(FiberRoot) =>
getNextLanes
renderRootSync(FiberRoot, Lanes) =>
set RenderContext as executionContext global
pushDispatcher (set ContextOnlyDispatcher as ReactCurrentDispatcher global)
prepareFreshStack(FiberRoot, Lanes) =>
createWorkInProgress => createFiber
workLoopSync =>
while workInProgress
performUnitOfWork(workInProgress) =>
beginWork(Firber.alternate (aka current), Fiber (aka workInProgress)) =>
switch by workInProgress.tag
(if HostRoot)
updateHostRoot =>
pushHostRootContext (set host implementation globals)
processUpdateQueue =>
getStateFromUpdate(Fiber, UpdateQueue, Update) =>
switch by Update.tag
(if UpdateState)
return `update.payload` (this case `{ element: ReactElement for `App` }`)
set newState to `UpdateQueue.baseState` and `workInProgress.memoizedState`
(if FiberRoot.isDehydrated)
enterHydrationState =>
set global `isDehydrating`, `nextHydratableInstance` (cf. ReactFiberHostConfig.getFirstHydratableChildWithinContainer) etc...
mountChildFibers => ...
set child fiber flag `Hydrating`
(otherwise)
reconcileChildren (reconcile `workInProgress.memoizedState.element` as new children) => reconcileChildFibers => ...
return workInProgress.child (i.e. newly reconciled fiber)
(if IndeterminateComponent)
mountIndeterminateComponent =>
renderWithHooks =>
set currentlyRenderingFiber
set ReactCurrentDispatcher to HooksDispatcherOnMount (or HooksDispatcherOnUpdate if `current` Fiber exists)
Component(props, ...) (where Component = `workInProgress.type` and props = `workInProgress.pendingProps`)
=> App ...
reset ReactCurrentDispatcher to ContextOnlyDispatcher
return children (ReactNode returned from Component) (here `App` returns a number `0`, which to be rendered as a text)
reconcileChildren => mountChildFibers => ...
(if FunctionComponent (i.e. rerendering the result of mountIndeterminateComponent))
updateFunctionComponent =>
renderWithHooks => ...
reconcileChildren
(if HostComponent)
updateHostComponent =>
(if current === null (i.e. initial rendering))
tryToClaimNextHydratableInstance =>
(if not `isHydrating`) return
tryHydrate =>
(if HostComponent)
ReactHostConfig.canHydrateInstance
set Fiber.stateNode to hydrated dom instance
getFirstHydratableChild as nextHydratableInstance
(if SuspenseComponent) TODO
reconcileChildren
set next `workInProgress` if `beginWork` returns new `Fiber` (e.g. `HostRoot` returning `IndeterminateComponent`)
otherwise completeUnitOfWork =>
loop
completeWork =>
switch by `Fiber.tag`
(if `HostText` (e.g. a text node "0" rendered by `App`)
getRootHostContainer
ReactFiberHostConfig.createTextInstance
(if `HostComponent`)
(if not first render (i.e. "current" Fiber is not null)
updateHostComponent (defined in ReactFiberCompleteWork.new.js different from the one in `beginWork`) => TODO
(otherwise)
(if wasHydrated)
prepareToHydrateHostInstance =>
ReactDOMHostConfig.hydrateInstance(fiber.stateNode (i.e. dom node), ...) =>
precacheFiberNode (inject fiber as "__reactFiber$..." property)
updateFiberProps (inject props as "__reactProps$..." property)
diffHydratedProperties (warn difference of fiber props and server-side-rendered attributes on dom node)
(otherwise)
ReactFiberHostConfig.createInstance
continue loop with `Fiber.siblings` or `Fiber.return` (i.e. parent) if any
commitRoot => commitRootImpl =>
set CommitContext
scheduleCallback (for flushPassiveEffects which includes `useEffect` callbacks)
commitBeforeMutationEffects =>
ReactFiberHostConfig.prepareForCommit
...
commitMutationEffects => commitMutationEffects_begin =>
for each Fiber in Fiber.deletions
commitDeletion =>
unmountHostComponents =>
loop child fibers of unmounted fiber (traverse from parent to children)
(if HostComponent/HostText)
ReactHostconfig.removeChildFromContainer (NOTE: `useEffect` cleanup is called after DOM is removed in `flushPassiveEffects`)
(otherwise)
commitUnmount =>
(if FunctionComponent)
loop Effect with `HookInsertion` or `HookLayout` tag (aka `useInsertionEffect`)
call Effect.destroy
commitMutationEffects_complete => commitMutationEffectsOnFiber =>
swith by `Fiber.flags`
(if Placement) commitPlacement => ... => ReactFiberHostConfig.appendChildToContainer
(if Hydrating) unset `Hydrating` flag
(if Update) commitWork =>
(if FunctionComponent)
commitHookEffectListUnmount (hook flags `HookInsertion | HookHasEffect` (aka `useInsertionEffect`))
commitHookEffectListMount
(if HostComponent)
ReactFiberHostConfig.commitUpdate
(if HostRoot)
(if FiberRoot.isDehydrated)
unset FiberRoot.isDehydrated
ReactFiberHostConfig.commitHydratedContainer
ReactFiberHostConfig.resetAfterCommit
commitLayoutEffects => ... => commitLayoutEffectOnFiber =>
(if HostComponent)
ReactFiberHostConfig.commitMount
flushPassiveEffects => flushPassiveEffectsImpl =>
set CommitContext
commitPassiveUnmountEffects =>
for each Fiber in Fiber.deletions (i.e. deleted children fibers)
commitPassiveUnmountEffectsInsideOfDeletedTree_begin =>
commitPassiveUnmountInsideDeletedTreeOnFiber => commitHookEffectListUnmount
commitPassiveUnmountEffects_begin =>
commitPassiveUnmountEffects_complete => commitPassiveUnmountOnFiber =>
commitHookEffectListUnmount (call `Effect.destroy` callback if any)
commitPassiveMountEffects => ... => commitPassiveMountOnFiber =>
(if FunctionComponent)
commitHookEffectListMount (only for hook flags `HookPassive | HookHasEffect`) =>
for each `Effect` in a linked list `Fiber.updateQueue.lastEffect`
call `Effect.create` (i.e. UseEffect callback) and asssign result to `Effect.destroy`
flushSyncCallbacks
component/hook level
(mount)
beginWork => mountIndeterminateComponent => renderWithHooks => App =>
React.useState => HooksDispatcherOnMountInDEV.useState =>
mountState =>
mountWorkInProgressHook (initialize `Hook` and set to `workInProgressHook` and `currentlyRenderingFiber.memoizedState`)
assign initial state to Hook.memoizedState
initialize `UpdateQueue` and assign it to `Hook.queue`
return [Hook.memoizedState, dispatchSetState (bound with `currentlyRenderingFiber` and `UpdateQueue`)]
React.useEffect => HooksDispatcherOnMountInDEV.useEffect => mountEffect =>
mountEffectImpl =>
mountWorkInProgressHook (initialize `Hook` and append to a linked list `workInProgressHook` except the 1st hook call)
add `Passive` flag to currentlyRenderingFiber.flags
pushEffect =>
initialize `Effect` and push to `currentlyRenderingFiber.updateQueue.lastEffect`
here `Effect.flags` is `HookPassive | HookHasEffect`
assign effect to Hook.memoizedState
(setState)
dispatchSetState =>
(NOTE: this routine looks very similar to `updateContainer` in `ReactUpdateQueue.new.js` but `Update` and `UpdateQueue` here are completely independent types from the ones used in `updateContainer`)
initialize `Update` and push to `Hook.queue`
scheduleUpdateOnFiber
(update)
beginWork => updateFunctionComponent => renderWithHooks => App =>
React.useState => HooksDispatcherOnMountInDEV.useState => updateState =>
updateReducer(basicStateReducer, ...) =>
updateWorkInProgressHook (restore `workInProgressHook` from `currentlyRenderingFiber.alternate.memoizedState`)
for each `Update` in `Hook.baseQueue` (there can be multiple `setState` calls)
reducer(Hook.baseState, action)
return [hook.memoizedState, hook.queue.dispatch]
React.useEffect => HooksDispatcherOnMountInDEV.useEffect => updateEffect =>
updateEffectImpl =>
updateWorkInProgressHook (restore `Hook` from a linked list `workInProgressHook`)
pushEffect (but without `HookHasEffect` flag if "deps" are same as previous render)
ref (cf. Ref in ReactFiberFlags.js)
(mount)
React.useRef => mountRef => return { current: (initialValue) }
(update)
React.useRef => updateRef => return Hook.memoizedState
commitLayoutEffectOnFiber => commitAttachRef =>
instance = Fiber.stateNode (i.e. dom node)
(if Fiber.tag is HostComponent) ReactFiberHostConfig.getPublicInstance(instance)
(if Fiber.ref is function)
call ref(instance)
(otherwise)
set ref.current = instance
commitDeletionEffectsOnFiber, commitMutationEffectsOnFiber, etc.. => safelyDetachRef => (call with null or set null)
reconcilation
reconcileChildFibers =>
(simple case where `currentFirstChild: null` and `newChild: ReactElement`)
reconcileSingleElement =>
createFiberFromElement =>
createFiberFromTypeAndProps (new `Fiber` with `IndeterminateComponent` tag and `App` type)
placeSingleChild
data structure
ReactElement
type, key, ref, props
FiberRoot
current: Fiber
Fiber
tag
type
flags (lifecycle flag e.g. Placement Update, Deletion)
pendingProps
updateQueue
child
return
sibling
stateNode
deletions (cf. deleteChild in ReactChildFiber.new.js)
memoizedState
Lane (bits contstants in react-reconciler/src/ReactFiberLane.new.js)
ExecutionContext (RenderContext, CommitContext, ...)
Hook
baseState, memoizedState
queue: UpdateQueue
dispatch
lastRenderedReducer (basicStateReducer by default)
lastRenderedState
Effect
create (callback passed via UseEffect)
destroy (return value of `create`)
# debug through legacy root api
yarn test --debug packages/react-dom/src/__tests__/ReactDOM-test.js -t 'should bubble onSubmit'
# entrypoint
ReactDOM.render =>
legacyRenderSubtreeIntoContainer (forceHydrate = false) =>
legacyCreateRootFromDOMContainer =>
createContainer (HTMLElement as `containerInfo`) [react-reconciler]
listenToAllSupportedEvents =>
for each event in `allNativeEvents`
listenToNativeEvent (on root HTMLElement) =>
addTrappedEventListener =>
createEventListenerWrapperWithPriority =>
getEventPriority (hard coded mapping of priorities)
choose global event handler based on priority (e.g. dispatchDiscreteEvent)
addEventListener on root container
flushSync => updateContainer [react-reconciler]
# render
ReactFiberHostConfig.createInstance =>
createElement (document.createElement with a few tricks)
updateFiberProps (sneak props into HTMLElement as "__reactProps...")
# browser event dispatch
dispatchDiscreteEvent =>
ReactCurrentBatchConfig.transition = 0
setCurrentUpdatePriority(DiscreteEventPriority)
dispatchEvent =>
dispatchEventWithEnableCapturePhaseSelectiveHydrationWithoutDiscreteEventReplay =>
findInstanceBlockingEvent (find relevant Fiber) =>
getEventTarget(nativeEvent)
getClosestInstanceFromNode (get Fiber from HTMLElement)
set found Fiber to return_targetInst global
dispatchEventForPluginEventSystem(... return_targetInst ...) =>
batchedUpdates => dispatchEventsForPlugins =>
extractEvents => SimpleEventPlugin.extractEvents(dispatchQueue, ...) =>
(e.g. if "click" event)
topLevelEventsToReactNames (map to "onClick")
get constructor SyntheticMouseEvent
accumulateSinglePhaseListeners (collect `onClick` props from target Fiber to root Fiber)
push SyntheticMouseEvent to dispatchQueue
processDispatchQueue =>
processDispatchQueueItemsInOrder =>
(if `stopPropagation` is already called) return
(otherwise) executeDispatch =>
invokeGuardedCallbackAndCatchFirstError (calling handler with try/catch)
server side rendering
yarn test --debug packages/react-dom/src/__tests__/ReactDOMServerIntegrationHooks-test.js -t 'basic render'
yarn test --debug packages/react-dom/src/__tests__/ReactServerRenderingHydration-test.js -t 'should warn when the style property differs'
------------
-- render --
------------
renderToStringImpl(ReactNodeList, ...) =>
createResponseState
createRequest(ReactNodeList, ...) => createTask
startWork => scheduleWork => performWork =>
set ReactCurrentDispatcher to Dispatcher in ReactFizzHooks.js (notably `useEffect` is noop)
retryTask(Request, Task) =>
renderNodeDestructive =>
(if REACT_ELEMENT_TYPE)
renderElement =>
# switch by type
(if functional component)
renderIndeterminateComponent =>
renderWithHooks (defined in ReactFizzzServer.js. this is not the one from react-conciler) =>
prepareToUseHooks
Component => ...
finishHooks
renderNodeDestructive (recursively for returned element)
(if dom)
renderHostElement =>
pushStartInstance (emit open tag with props as attributes (except `children`))
renderNode (for children) => renderNodeDestructive
pushEndInstance (emit close tag)
(if REACT_SUSPENSE_TYPE) TODO
(if REACT_LAZY_TYPE) TODO
startFlowing => flushCompletedQueues
------------------------------
-- hook (ReactFizzzHook.js) --
------------------------------
useState => useReducer =>
createWorkInProgressHook
(if isReRender)
TODO: is this relevant only when using `Suspense`?
(otherwise)
evaluate initial state
------------------------------------------------
-- hydration (see beginWork/commitWork above) --
------------------------------------------------
ReactDOM.hydrate =>
legacyRenderSubtreeIntoContainer (forceHydrate = true) =>
legacyCreateRootFromDOMContainer(..., forceHydrate) =>
(if not forceHydrate) clear children dom
createContainer (create FiberRoot with isDehydrated = true)
--------------------
-- data structure --
--------------------
Request
Destination
pingedTasks Array<Task>
Task
ReactNodeList