└── README.md /README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | Just another one of my **Self-Study Projects** . I don't tend to make such projects public but this might be of help for someone looking to understand Fiber and React Under the hood. Do remember as of writing this, this is my first time looking at the code for Fiber. 4 | 5 | Often times reading a large amount of code can be very intimidating for someone new to programming. So it's best to read each line and document them if needed so you don't go hoping around files searching for that lost variable or get lost on the Import tags. There is no set rules or anything here, just documenting/laying it out the best I can. Any part of this doc might change as my understanding of the whole system grows. 6 | 7 | In order to properly use this, clone it and uncheck all the files and then go through the files one by one and adjust the pseudocode to your liking. Remember it took them 2+ years to make Fiber so be patient as it might take you a while. 8 | 9 | **As for obvious errors and such:** 10 | 11 | > “There are no mistakes, only happy accidents.” Bob Ross 12 | 13 | 14 | 15 | **Lot of this comes directly from Fiber codebase itself, a lot of it will be just code reorganized for easier understanding. I Turn a lot of functions into Pseudocode for easier reference and understanding** 16 | 17 | Here is the link for the official codebase [REACT](https://github.com/facebook/react/tree/master/packages/react-reconciler) 18 | 19 | ### Files So Far 20 | 21 | 22 | - [x] ReactFiber.js > Defining Functions For Creating Fibers 23 | - [x] ReactCurrentFiber.js > Functions for Describing Fibers 24 | - [ ] ReactDebugFiberPerf > probably for debugging DEV 25 | - [x] ReactChildFiber > The Bulk of The Child Reconciliation 26 | - [x] ReactClassComponent.js > Class mounting, class instances, class reconciliation 27 | - [x] ReactContext.js > Probably context stuff 28 | - [x] ReactFiberHostContext > Host Stuff 29 | - [ ] ReactFiberHydrationContext.js > Must Understand the Fast and Furious DOM 30 | - [x] ReactFiberPendingPriority.js > Priority, marks levels for various stuff 31 | - [x] ReactFiberRoot.js > For creating Fiber Roots 32 | - [x] ReactFiberExpirationTime.js > How expiration time is handled 33 | - [ ] ReactProfilerTimer.js > For Recording/Tracking Time React Profiler 34 | - [ ] ReactFiberTreeReflection.js > Finding Where the host fibers are...nature 35 | - [x] ReactFiberStack.js > Concerns how the "stack" is shifted 36 | - [ ] ReactFiberScheduler > THIS IS SOME GUCCI CODE 37 | - [x] ReactUpdateQueue.js > Scheduling The Updates(or I think so) 38 | - [x] ReactFiberBeginWork.js > This is for begining the work 39 | - [x] ReactCompleteWork.js > CompleteWork 40 | - [x] ReactUnwindWork.js > Unwind 41 | - [ ] ReactCommitWork.js > Committing Work 42 | - [x] ReactFiberReconciler.js > The Main File 43 | - [ ] REACT-DOM.JS > Where the above files end making sense 44 | - [ ] Scheduler.js > located in a different package > concerns RequestAnimationFrame 45 | 46 | ----------------- 47 | 48 | ### Note on Algorithms Used 49 | 50 | I thought there would be many complicated algorithms used since it took 2+ years to build. 51 | These Are All the ones I noticed and are quite simple 52 | 53 | ``` 54 | 55 | SinglyLinked Lists 56 | Simple Stacks (push) (pop) are used based on a index/cursor. 57 | The algorithms used for reconciling children are equivalent of going through a JSON file. 58 | Simply mapping it out, maybe run some while loops on the childrens until all are reconciled. 59 | Building a clone then see what changed and perform replacements. 60 | Navigating through the stack in reverse or forward direction 61 | 62 | ``` 63 | 64 | 65 | ---------------- 66 | 67 | 68 | ## What Is A Fiber and How Are They Created? 69 | 70 | > A Fiber is work on a Component that needs to be done or was done. There can be more than one per component. 71 | 72 | It is essentially an object with set properties used to identify what kind of a (component) it is and what works needs to be done. Assign prioirity levels for efficient processing. since it uses flow, it is also exported as a type. 73 | 74 | Most/if not all is in: **ReactFiber.js** 75 | 76 | ``` 77 | 78 | tag: Tag identifying the type of fiber 79 | 80 | key: Unique identifier of this child 81 | 82 | type: The function/class/module associated with this fiber 83 | 84 | stateNode: The local state associated with this fiber 85 | 86 | return: The Fiber to return to after finishing processing this one 87 | 88 | child: Singly Linked List Tree Structure 89 | 90 | sibling: Singly Linked List Tree Structure 91 | 92 | index: Singly Linked List Tree Structure 93 | 94 | ref: The ref last used to attach this node 95 | 96 | pendingProps: Input is the data coming into process this fiber/Arguments/Props 97 | 98 | memoizedProps: The props used to create the output 99 | 100 | updateQueue: A queue of state updates and callbacks 101 | 102 | memoizedState: The state used to create the output 103 | 104 | firstContextDependency: A linked-list of contexts that this fiber depends on 105 | 106 | mode: (Bitfield) that describes properties about the fiber and its subtree 107 | 108 | effectTag: Effect 109 | 110 | nextEffect: Singly linked list fast path to the next fiber with side-effects 111 | 112 | firstEffect: The first fiber with side-effect within this subtree 113 | 114 | lastEffect: The last fiber with side-effect within this subtree 115 | 116 | expirationTime: Represents a time in the future by which this work should be completed 117 | 118 | childExpirationTime: This is used to quickly determine if a subtree has no pending changes 119 | 120 | alternate: A pooled version of a Fiber-Every fiber that gets updated will 121 | 122 | actualDuration: Time spent rendering this Fiber and its descendants for the current update 123 | 124 | actualStartTime: This marks the time at which the work began(only if Fiber is active in render phase) 125 | 126 | selfBaseDuration: Duration of the most recent render time for this Fiber 127 | 128 | treeBaseDuration: Sum of base times for all descedents of this Fiber. 129 | ``` 130 | 131 | While in DEV Mode, additional properties for (tracking) fibers 132 | ``` 133 | _debugID 134 | _debugSource 135 | _debugOwner 136 | _debugIsCurrentlyTiming 137 | ``` 138 | Function **FiberNode** is incharge of creating the Fiber, which has a constructor function **createFiber** returning Fibernode . Both are relying on the following parameters 139 | 140 | ``` 141 | tag, 142 | pendingProps, 143 | key, 144 | mode 145 | ``` 146 | Function **createWorkInProgress** is used to create an alternate fiber.(Pooling). Uses a double buffering pooling technique. 147 | ``` 148 | 3 parameters (current, pendingProps, expirationTime) 149 | making a work in progress fiber 150 | 151 | if workInprogress = null 152 | createFiber (new) 153 | else 154 | reset effect tags on the alternate 155 | the rest of work in progress fiber 156 | ``` 157 | 158 | Function(exported) **createFiberFromElement**. Hmm look this is incharge of creating all the fibers based on the React element type. 159 | ``` 160 | parameters(element, mode, expirationTime) 161 | 162 | type = element.type 163 | fiberTag 164 | if 165 | function > shouldConstruct ? ClassComponent : InderterminateComponent 166 | elif 167 | string > HostComponent 168 | else 169 | switch: case depending on type 170 | REACT_FRAGMENT_TYPE > return createFiberFromFragment(pendingProps, mode, expirationTime, key) 171 | REACT_CONCURRENT_MODE_TYPE > ConcurrentMode 172 | REACT_STRICT_MODE_TYPE > StrictMode 173 | REACT_PROFILER_TYPE > createFiberFromProfiler(pendingProps, mode, expirationTime, key) 174 | REACT_PLACEHOLDER_TYPE > PlaceholderComponent 175 | default 176 | REACT_PROVIDER_TYPE > ContextProvider 177 | REACT_CONTEXT_TYPE > ContextConsumer 178 | REACT_FORWARD_REF_TYPE > ForwardRef 179 | REACT_PURE_TYPE > PureComponent 180 | invariant 181 | createFiber(fiberTag, pendingProps, key, mode) 182 | return the created fiber 183 | 184 | ``` 185 | ReactWorkTags For Quick Reference 186 | 187 | ``` 188 | FunctionComponent = 0; 189 | FunctionComponentLazy = 1; 190 | ClassComponent = 2; 191 | ClassComponentLazy = 3; 192 | IndeterminateComponent = 4; 193 | HostRoot = 5; 194 | HostPortal = 6; 195 | HostComponent = 7; 196 | HostText = 8; 197 | Fragment = 9; 198 | Mode = 10; 199 | ContextConsumer = 11; 200 | ContextProvider = 12; 201 | ForwardRef = 13; 202 | ForwardRefLazy = 14; 203 | Profiler = 15; 204 | PlaceholderComponent = 16; 205 | PureComponent = 17; 206 | PureComponentLazy = 18; 207 | 208 | also MAX_SIGNED_31_BIT_INT = 1073741823 209 | ``` 210 | 211 | Following Functions create different type of fibers, assign a type and expirationTime. Then using **createFiber** , return a Fiber. 212 | 213 | ``` 214 | createFiberFromFragment 215 | createFiberFromProfiler 216 | createFiberFromText 217 | createFiberFromHostInstanceForDeletion 218 | createFiberFromPortal 219 | createHostRootFiber 220 | ``` 221 | 222 | Function **assignFiberPropertiesInDev** : Used for stashing WIP properties to replay failed work in DEV. 223 | 224 | ---------------- 225 | 226 | ### What Are Root Fibers? 227 | 228 | **ReactFiberRoot.js** 229 | 230 | The Host Fiber and it has the following properties and is created by **createFiberRoot** 231 | 232 | ``` 233 | current: The currently active root fiber. This is the mutable root of the tree. 234 | 235 | containerInfo: Any additional information from the host associated with this root. 236 | 237 | pendingChildren: 238 | 239 | earliestSuspendedTime: earliest priority levels that are suspended from committing. 240 | latestSuspendedTime: latest priority levels that are suspended from committing. 241 | 242 | earliestPendingTime: earliest priority levels that are not known to be suspended. 243 | latestPendingTime: latest priority levels that are not known to be suspended. 244 | 245 | latestPingedTime: The latest priority level that was pinged by a resolved promise and can be retried. 246 | 247 | didError: if error thrown 248 | pendingCommitExpirationTime: 249 | 250 | finishedWork: A finished work-in-progress HostRoot that's ready to be committed. 251 | 252 | timeoutHandle: Timeout handle returned by setTimeout. Used to cancel a pending timeout 253 | 254 | context: Top context object, used by renderSubtreeIntoContainer 255 | pendingContext: 256 | 257 | +hydrate: Determines if we should attempt to hydrate on the initial mount 258 | 259 | nextExpirationTimeToWorkOn: Remaining expiration time on this root. 260 | 261 | expirationTime: 262 | 263 | firstBatch: 264 | ``` 265 | 266 | ---------------- 267 | 268 | ### Expiration Time 269 | 270 | **ReactFiberExpirationTime.js** 271 | 272 | Contains functions concerning ExpirationTime and when low priority and high priority Batches. High priority Batches have a longer expiration time than low priority while in DEV mode. 273 | 274 | ``` 275 | 1 unit of expiration time represents 10ms 276 | 277 | msToExpirationTime > returns ms/10 + 2 278 | expirationTimeToMS > return (expirationTime - 2) * 10 279 | 280 | ``` 281 | 282 | High and Low Priority Times in ms 283 | 284 | ``` 285 | LOW_PRIORITY_EXPIRATION = 5000 286 | LOW_PRIORITY_BATCH_SIZE = 250 287 | HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150 288 | HIGH_PRIORITY_BATCH_SIZE = 100 289 | ``` 290 | 291 | High Priority Uses the Function **computeInteractiveExpiration** with the currentTime. Low priority uses **computeAsyncExpiration**, likely used for offscreen renders. Both use the function **computeExpirationBucket** (currentTime, expirationInMs, bucketSizeMs). 292 | 293 | ---------------- 294 | 295 | ### Methods For Detecting Fiber In A Stack 296 | 297 | **ReactCurrentFiber.js** 298 | 299 | NOTE: Recheck when this is used 300 | 301 | The main function for describing what the fiber type is **describeFiber** 302 | ``` 303 | parameters(Fiber) 304 | switch (depending on the tag) 305 | IndeterminateComponent 306 | FunctionComponent 307 | FunctionComponentLazy 308 | ClassComponent 309 | ClassComponentLazy 310 | HostComponent 311 | Mode 312 | name = getComponentName //Function returning a string based on Types(CAP_NAMES)-paramters is type 313 | if 314 | ownerName = getComponentName 315 | return describeComponentFrame(name, source, ownerName) 316 | ``` 317 | 318 | So **describeFiber** is used in a exported function **getStackByFiberInDevAndProd** 319 | 320 | ``` 321 | do 322 | info += describeFiber(node); 323 | while workInProgress 324 | return info 325 | ``` 326 | 2 exports being the current Fiber and LifeCyclePhase 327 | 328 | Maybe add DEV stuff if they are needed 329 | 330 | ------------ 331 | 332 | 333 | ### How The Stack Works 334 | 335 | **ReactFiberStack.js** 336 | 337 | NOTE: Updates are not sorted by priority, but by insertion; new updates are always appended to the end of the list. 338 | 339 | The stack itself seems to be a push pop based on the index(initial value of -1), either increment the index when pushing or decrement when popping. 340 | ``` 341 | const valueStack [] 342 | let fiberStack [] | null 343 | 344 | let index = -1 345 | 346 | createCursor for assigning where to start looking in the stack 347 | 348 | pop(cursor, fiber) 349 | if index < 0 return '' 350 | current cursor = valueStack[index] 351 | assign null to valueStack since it's removed from stack 352 | index-- 353 | 354 | push(cursor, value, fiber) 355 | index++ 356 | valueStack[index] is the current cursor 357 | current cursors is assigned the new value 358 | 359 | ``` 360 | 361 | ---------------- 362 | 363 | ### How The ClassComponent Works(Updated and such) 364 | 365 | **ReactFiberClassComponent.js** 366 | 367 | this is incharge of handling how the react components are updated: 368 | 369 | the outer function incharge of sending props to the internal state is **applyDerivedStateFromProps**. Which handles 370 | Merging of the partial state and the previous state. Where memoized state is compared with the previous and using methods like Redux object.assign. 371 | It assigns a new state. There is a if case present that if it's not in a updatequeue then basestate(initial state) = memiozed state. 372 | 373 | 374 | The structure of this is a lot like using Redux(payloads, assigning) 375 | 376 | There is a main classComponentUpdater which is contains various functions: 377 | 378 | ``` 379 | 380 | isMounted 381 | 382 | enqueueSetState(inst, payload, callback) 383 | get various values on current time and expirationtimes for the given Fiber 384 | createUpdate 385 | enqueueUpdate > takes in Fiber and the update which was just created 386 | scheduleWork > schedule it for work (fiber and the expirationTime) 387 | enqueueReplaceState 388 | Same as above function but for replacing state 389 | enqueueForceUpdate 390 | Same as above but for forced updates 391 | ``` 392 | 393 | Function for checking is the component should update **checkShouldComponentUpdate** 394 | 395 | ``` 396 | parameters(workInProgress, ctor, oldprops, newProps, oldState, newState, nextContext) 397 | const instance is the workinProgress 398 | if it's function 399 | startPhaseTimer 400 | shouldUpdate > also the returned value 401 | stopPhaseTimer 402 | if it's a pure component 403 | return if they !shallowEqual 404 | ``` 405 | function **adoptClassInstance** 406 | 407 | ``` 408 | 409 | // The instance needs access to the fiber so that it can schedule updates 410 | 411 | instance.updater = classComponentUpdater 412 | 413 | ``` 414 | 415 | The main function for constructing the current class instance **constructClassInstance** 416 | 417 | ``` 418 | parameters(workInProgress, ctor, props, renderExpirationTime) 419 | let isLegacyContextConsumer = false 420 | let unmaskedContext = emptyContextObject 421 | let context = null 422 | contextType = ctor.contextType 423 | if contextType is an object and true 424 | context = contextType.unstable_read() 425 | else 426 | unmaskedContext = getUnmaskedContext(workInProgress, ctor, true) 427 | const contextTypes = ctor.contextTypes 428 | set isLegacyContextConsumer to (true?) depending on the contextTypes being present 429 | context is getMaskedContext if isLegacyContextConsumer is true 430 | 431 | const instance = new ctor(props, context) 432 | the new state is equal to the memoizedState if the state of the current instance is not null 433 | adoptClassInstance(workInProgress, instance) 434 | if isLegacyContextConsumer is true then cacheContext(workInProgress, unmaskedContext, context) 435 | 436 | return instance 437 | 438 | ``` 439 | 440 | Various calls on the Component 441 | 442 | ``` 443 | 444 | callComponentWillMount(workInProgress, instance) 445 | startPhaseTimer 446 | old is instance state 447 | depending on the typeof instance call either componentWillMount or UNSAFE_componentWillMount 448 | stopPhaseTimer 449 | 450 | if oldstate !== instance.state 451 | 452 | callComponentWillRecieveProps(workInProgress, instance, newProps, nextContext) 453 | startPhaseTimer 454 | old is instance state 455 | depending on the typeof instance call either componentWillReceiveProps or UNSAFE_componentWillReceiveProps 456 | stopPhaseTimer 457 | 458 | if instance.state !== oldState then enqueueReplaceState(instance, instance.state, null) 459 | 460 | ``` 461 | 462 | Once the class instance is constructed then it needs to be mounted with **mountClassInstance** 463 | 464 | ``` 465 | 466 | parameters(workInProgress, ctor, newProps, renderExpirationTime) 467 | const instance = workInProgress.stateNode; 468 | instance.props = newProps; 469 | instance.state = workInProgress.memoizedState; 470 | instance.refs = emptyRefsObject; 471 | 472 | contextType = ctor.contextType 473 | if object and not null then instance.context does a unstable_read() 474 | else 475 | const unmaskedContext = getUnmaskedContext(workInProgress, ctor, true); 476 | instance.context = getMaskedContext(workInProgress, unmaskedContext); 477 | 478 | let updateQueue = workInProgress.updateQueue 479 | if not null then processUpdateQueue 480 | and the instance state will equal memoizedState 481 | if ctor.getDerivedSTateFromProps is true(function) then applyDerivedStateFromProps 482 | and instance state is the memoizedState 483 | ``` 484 | still need to understand these 485 | 486 | **resumeMountClassInstance** 487 | 488 | **updateClassInstance** 489 | 490 | -------------------------- 491 | 492 | ### How Child Fibers Are Reconciled 493 | 494 | **ReactChildFiber.js** 495 | 496 | There is one main function **ChildReconciler** which does most of the work. There are multiple inner functions that make up this entire function. 497 | 498 | ``` 499 | 500 | parameters(shouldTrackSideEffects) 501 | 502 | deleteChild(returnFiber, childToDelete) 503 | // Deletions are added in reversed order 504 | Uses effectTag to determine what child to delete 505 | if there is no nextEffect then null otherwise 506 | 507 | deleteRemainingChildren(returnFiber, currentFirstChild) 508 | using the currentFirstChild, 509 | it uses a while cascades unto next child until all are deleted uding deleteChild 510 | 511 | mapRemainingChildren(returnFiber, currentFirstChild) 512 | // Add the remaining children to a temporary map 513 | let existing be the current first 514 | while existing is not null 515 | if key present > existingChildren.set(existingChild.key(if no key then index), existingChild) 516 | existingChild = sibling 517 | return existingChildren 518 | 519 | useFiber(fiber, pendingProps, expirationTime) 520 | creates a fiber clone using createWorkInProgress and sets the index to 0 and sibling to null 521 | 522 | placeChild(newFiber, lastPlacedIndex, newIndex) 523 | const current = alternate of the new fiber 524 | if current !null oldindex is current 525 | if < lastPlacedIndex newFiber.effectTag is the new Placement 526 | else return oldIndex 527 | // This is an insertion. 528 | newFiber.effectTag = Placement; 529 | return lastPlacedIndex; 530 | 531 | placeSingleChild(newFiber) 532 | 533 | updateTextNode(returnFiber, current, textContent, expirationTime) 534 | if current null then return a new fiber using 535 | createFiberFromText 536 | else update the existing using 537 | useFiber 538 | 539 | updateElement(returnFiber, current, element, expirationTime) 540 | if current !== null and has a type then move it based on index 541 | useFiber 542 | coerceRef 543 | return existing fiber with a updated .ref .return 544 | else insert and create a fiber using 545 | createFiberFromElement 546 | coerceRef 547 | return the createdFiber 548 | 549 | updatePortal(returnFiber, current, portal, expirationTime) 550 | if null tag != HostPortal(worktag) 551 | insert a new createdfiber using createFiberFromPortal and return it 552 | else update it using useFiber and return it 553 | 554 | updateFragment(returnFiber, current, fragment, expirationTime, key) 555 | if null no tag != fragment 556 | insert createFiberFromFragment and return it 557 | else update useFiber 558 | 559 | createChild(returnFiber, newChild, expirationTime) 560 | // Text nodes don't have keys. 561 | if typeOf 'string' or 'number' return using createFiberFromText 562 | if typeOf 'object and !==null 563 | switch depending on newChild.$$typeof 564 | REACT_ELEMENT_TYPE > createFiberFromElement > coerceRef > return 565 | REACT_PORTAL_TYPE > createFiberFromPortal > coerceRef > return 566 | 567 | if newChild is an array or has iteration > createFiberFromFragment 568 | else error throwOnInvalidObjectType 569 | otherwise return null 570 | 571 | updateSlot(returnFiber, OldFiber, newChild, expirationTime) 572 | // Update the fiber if the keys match, otherwise return null and remember text nodes don't have keys 573 | 574 | if typeof newChild is 'string' or 'number' > return updateTextNode 575 | if key !== null > null 576 | if typeof newChild is 'object' and !== null 577 | switch depending on newChild.$$typeof 578 | REACT_ELEMENT_TYPE > updateElement 579 | if type is REACT_FRAGMENT_TYPE > return updateFragment 580 | REACT_PORTAL_TYPE > if newchild.key matches > updatePortal 581 | 582 | if newChild is an array or has iteration > updateFragment 583 | throw if none of the cases throwOnInvalidObjectType 584 | 585 | // The only difference is the newIdx that was created when mapping new children using mapRemainingChildren 586 | updateFromMap(existingChildren, returnFiber, newIdx, newChild, expirationTime) 587 | if typeof newChild is 'string' or 'number' > updateTextNode 588 | // const matchedFiber = existingChildren.get(newIdx) 589 | 590 | if typeof newChild is 'object' and !== null 591 | switch depending on newChild.$$typeof 592 | REACT_ELEMENT_TYPE > updateElement 593 | if type is REACT_FRAGMENT_TYPE > return updateFragment 594 | REACT_PORTAL_TYPE > if newchild.key matches > updatePortal 595 | 596 | // Not Sure if this is accurate, FIX LATER 597 | reconcileChildrenArray(returnFiber, currentFirstChild, newChildren, expirationTime) 598 | using a For Loop this function will go through each children and their children 599 | it will compare with created map Idx? and alse keep track using a new Idx 600 | updateSlot and if there is newFiber break the loop 601 | placeChild in the correct index 602 | 603 | if the newIdx = newChildren.length then deleteRemainingChildren 604 | if oldFiber is null > createChild > placeChild 605 | 606 | // Add all children to a key map for quick lookups. 607 | const existingChildren = mapRemainingChildren(returnFiber, oldFiber); 608 | 609 | use another For Loop and Keep scanning and use the map to restore deleted items as moves. 610 | 611 | finally return resultingFirstChild 612 | 613 | reconcileChildrenIterator(returnFiber, currentFirstChild, newChildrenIterable, expirationTime 614 | as per comments 615 | // This is the same implementation as reconcileChildrenArray(), 616 | // but using the iterator instead. 617 | 618 | reconcileSingleTextNode(returnFiber, currentFirstChild, textContent, expirationTime) 619 | if currentFirstChild != null and tag matches HostText 620 | use deleteRemainingChildren > useFiber > return 621 | deleteRemainingChildren 622 | createFiberFromText > return it 623 | 624 | reconcileSingleElement(returnFiber, currentFirstChild, element, expirationTime) 625 | while currentFirstChild !== null 626 | deleteRemainingChildren > useFiber > coerceRef else deleteChild 627 | return > child.sibling 628 | // repeat until no siblings 629 | if element.type is REACT_FRAGMENT_TYPE > return createFiberFromFragment 630 | else createFiberFromElement > coerceRef > return 631 | 632 | reconcileSinglePortal(returnFiber, currentFirstChild, portal, expirationTime) 633 | same as above SingleElement but Portals 634 | 635 | reconcileChildFibers(returnFiber, currentFirstChild, newChild, expirationTime) 636 | typeof newChild === 'object' and !== null; 637 | REACT_ELEMENT_TYPE > placeSingleChild(reconcileSingleElement) 638 | REACT_PORTAL_TYPE > placeSingleChild(reconcileSinglePortal) 639 | if typeof newChild is 'string' or 'number' > placeSingleChild(reconcileSingleTextNode) 640 | if newChild isArray > reconcileChildrenArray 641 | if newChild isiterable > reconcileChildrenIterator 642 | 643 | 644 | deleteReamainingChildren 645 | 646 | finally it returns reconcileChildFibers 647 | 648 | cloneChildFibers(current, workInProgress) 649 | creates a newchild by using createWorkInProgress(currentChild, currentChild.pendingProps, currentChild.expirationTime) 650 | workInProgress.child = newChild 651 | 652 | Run a while loop on the children and go to next chid, clone it until none 653 | 654 | ``` 655 | Even though this function is MASSIVE, all it really does is implement Georgia Bush's NO CHILD LEFT BEHIND POLICY 656 | 657 | ---------------- 658 | 659 | ### How Assigning Priority Works 660 | 661 | ReactFiberPendingPriority.js 662 | 663 | Most of these functions just if there is work to be done and compare it to expirationTime and Mark different priroity. 664 | Looks complicated but once you read it a few times, it's quite simple. The abbreviations should help in remebering it cause putting the whole word just confuses the brain for like no reason. 665 | 666 | ``` 667 | // abbreviations 668 | 669 | expirationTime = ET 670 | erroredExpirationTime = eET 671 | earliestPendingTime = ePT 672 | latestPendingTime = lPT 673 | earliestRemainingTime = eRT 674 | earliestSuspendedTime = eST 675 | latestSuspendedTime = lST 676 | pingedTime = Pi 677 | latestPingedTime = lPi 678 | renderExpirationTime = rET 679 | 680 | markPendingPriorityLevel(root, expirationTime) 681 | // Update the latest and earliest pending times 682 | 683 | const ePT is root's ePT 684 | if NoWork then roots ePT is the ET 685 | else if ePT > ET then root's ePT is ET 686 | else 687 | const lPT is root's lPT 688 | if lPT < ET then root's lPT is ET 689 | 690 | findNextExpirationTimeToWorkOn(expiration, root) 691 | 692 | // This functions main purpose is to findNextExpirationToWorkOn 693 | // but if certain conditions on the root are present then markPendingPriorityLevel 694 | 695 | markCommittedPriorityLevels(root, earliestRemainingTime) 696 | 697 | if eRT = NoWork then clear root's ePT, lPT, eST, lST, lPi 698 | and findNextExpirationTimeToWorkOn(NoWork, root) 699 | 700 | const lPT is root's lPT 701 | if lPT !== NoWork 702 | if lPT < eRT then root's has no work 703 | else 704 | const ePT is root's ePT 705 | if ePT < eRT then root's ePT = lPT 706 | 707 | 708 | const eST is root's eST 709 | if eST has NoWork then 710 | markPendingPriorityLevel 711 | findNextExpirationTimeToWorkOn 712 | 713 | const lST is root's lST 714 | if eRT > lST then root's eST, lST, lPi = NoWork 715 | markPendingPriorityLevel 716 | findNextExpirationTimeToWorkOn 717 | 718 | if eRT < eST 719 | markPendingPriorityLevel 720 | findNextExpirationTimeToWorkOn 721 | 722 | 723 | findNextExpirationTimeToWorkOn 724 | 725 | hasLowerPriorityWork(root, erroredExpirationTime) 726 | assigns const lPT, lST, lPI to Root's counterparts 727 | returns a true by checking if any of the above const have NoWork 728 | and any of time are greater then the eET 729 | // thus assigning it low priroity value 730 | 731 | isPriorityLevelSuspended(root, expirationTime) 732 | const eST, lST = root's eST, lST 733 | return true if eST != NoWork and ET >= eST and ET <=lST 734 | 735 | markSuspendedPriorityLevel(root, suspendedTime) 736 | clearPing(root, suspendedTime) //this function checks if the root was pinged during render phase 737 | 738 | // This is a same function as markCommittedPriorityLevels but with ST and without marking priority levels 739 | 740 | findNextExpirationTimeToWorkOn(suspendedTime, root) 741 | 742 | markPingedPriorityLevel(root, pingedTime) 743 | const lPi is root's lPi 744 | if lPi has NoWork or lPi < pingedTime then root's lPi is Pi 745 | findNextExpirationTimeToWorkOn 746 | 747 | findEarliestOutstandingPriorityLevel(root, renderExpirationTime) 748 | let eET = rET 749 | const ePT, eST = root's ePT, eST 750 | if NoWork and eST < eET then eET is ePT 751 | return earliestExpirationTime; 752 | 753 | didExpireAtExpiration(root, currentTime) 754 | root's nextExpirationTimeToWorkOn = currenTime 755 | 756 | 757 | findNextExpirationTimeToWorkOn(completedExpirationTime, root) 758 | const eST, lST, ePT,lPi = root's counterparts 759 | 760 | // Work on the earliest pending time. Failing that, work on the latest pinged time 761 | 762 | if nextExpirationTimeToWorkOn = NoWork then it = lST 763 | let ET = nextExpirationTimeToWorkOn 764 | if NoWork then Et is eST 765 | 766 | root.nextExpirationTimeToWorkOn = nextExpirationTimeToWorkOn; 767 | root.expirationTime = expirationTime; 768 | 769 | ``` 770 | ---------------- 771 | 772 | ### How The Update Queue Works 773 | 774 | **ReactUpdateQueue** 775 | 776 | This is a very interesting file because it creates not one but 2 queues. There is a main function **enqueueUpdate** which is responsible for creating those two queues. It has a very nice system, it checks if queue1 is present based on the Fiber's updateQueue. If the queue1 is null then it creates the queue1. The interesting part is that it creates a clone of the same queue obased on the fiber.alternate updateQueue. There is a function for creating the queue **createUpdateQueue**. There are multiple if within the enqueueUpdate such as if both queues are null then create both and if either is null then create clone of the using cloneUpdateQueue. It gets even more interesting because there is a function called **appendUpdateToQueue** which appends the updates to both queues. During processing of the update queue, there is a inner function which checks if the WIP queue is a Clone. 777 | 778 | The UpdateQueue, also exported as a type. Intial values listed 779 | 780 | ``` 781 | baseState, 782 | firstUpdate: null, 783 | lastUpdate: null, 784 | firstCapturedUpdate: null, 785 | lastCapturedUpdate: null, 786 | firstEffect: null, 787 | lastEffect: null, 788 | firstCapturedEffect: null, 789 | lastCapturedEffect: null, 790 | 791 | ``` 792 | 793 | The update type 794 | 795 | ``` 796 | expirationTime: ExpirationTime, 797 | 798 | // Tags below 799 | tag: 0 | 1 | 2 | 3, 800 | payload: any, 801 | callback: (() => mixed) | null, 802 | 803 | next: Update | null, 804 | nextEffect: Update | null, 805 | 806 | ``` 807 | 808 | 809 | The exported constants for reference 810 | 811 | ``` 812 | 813 | UpdateState = 0; 814 | ReplaceState = 1; 815 | ForceUpdate = 2; 816 | CaptureUpdate = 3; 817 | 818 | 819 | ``` 820 | One of the coolest functions is **getStateFromUpdate**, which works a lot like Redux. 821 | 822 | **getStateFromUpdate** 823 | 824 | ``` 825 | 826 | parameters(workInProgress, queue, update, prevState, nextProps, instance) 827 | switch case depending on the update.tag 828 | ReplaceState 829 | const payload = update.payload 830 | if the payload is a 'function' then return a payload.call(instance, prevState, nextProps) 831 | return payload 832 | CaptureUpdate 833 | WIP effectTag 834 | UpdateState 835 | const payload 836 | let partialState 837 | if payload is a 'function' the partialState = payload.call as above 838 | else partialState is payload 839 | if partialState is null or undefined then return the prevState 840 | 841 | return Object.assign({}, prevState, partialState) 842 | 843 | // I wonder if it's possible to put a global state here and natively implement Redux 844 | 845 | ForceUpdate > hasForceUpdate > return prevState 846 | return prevState 847 | ``` 848 | 849 | **commitUpdateQueue** 850 | 851 | ``` 852 | parameters(finishedWork, finishedQueue, instance, renderExpirationTime) 853 | check is the finishedQueue.firstCapturedUpdate isn't null 854 | if finQeue's lastUpdate isn't null then lastUpdate.next is the firstCapturedUpdate 855 | and the lastUpdate is lastCapturedUpdate 856 | firstCapturedUpdate > lastCapturedUpdate > null 857 | commitUpdateEffects then set 1stEffect > LastEffect > null 858 | commitUpdateEffects then set 1stCapturedEffect > lastCapturedEffect > null 859 | 860 | commitUpdateEffects(effect, instance) 861 | while callback hell 862 | ``` 863 | 864 | 865 | ----------------------- 866 | 867 | ### How Context Is Handled 868 | 869 | **ReactFiberContext.js** 870 | 871 | Most of these functions from the looks of it just seem to be concerned with getting the context from the Fibers. 872 | 873 | There seems to be a general pattern emerging. 874 | Which is get something from a stack, store/cache, reconcile and then put it back in the stack. 875 | 876 | So there is a function for getting the context from the current WIP fiber and acting on it. 877 | 878 | **getUnmaskedContext** and **getMaskedContext** return the context. 879 | 880 | **cacheContext** is reponsible for storing the context and assigning it to the WIP.stateNode 881 | 882 | **hasContextChanged** returns a boolean and checks if work was performed while it's in the Stack 883 | 884 | The following 4 functions manage the Push Pop actions on the stackCursor 885 | 886 | ``` 887 | 888 | popContext 889 | popTopLevelContextObject 890 | pushTopLevelContextObject > double push to contextStack and didWIP 891 | pushContextProvider > double push using previousContext 892 | 893 | ``` 894 | 895 | **processChildContext** 896 | 897 | ``` 898 | 899 | parameters(fiber, type, parentContext) 900 | const instance = fiber.stateNode; 901 | const childContextTypes = type.childContextTypes; 902 | let childContext 903 | get the instance of the child context using the stop/start PhaseTimers 904 | return {...parentContext, ...childContext}; 905 | ``` 906 | 907 | There is a function **invalidateContextProvider** that checks if change occured, then processChildContext and perform a the replacement on the stack using pop > pop and then push > push. If no change then a simple pop push. 908 | 909 | **findCurrentUnmaskedContext** 910 | 911 | ``` 912 | 913 | parameters(fiber) 914 | // assigns a node variable and checks the tag 915 | // lazy component uses getResultFromResolvedThenable comes from shared file ReactLazyComponent 916 | do 917 | switch depending on tag 918 | HostRoot > return stateNode.context 919 | ClassComponent > using isContextProvider > return MemoizedMergedChildContext 920 | ClassComponentLazy > same as ClassComponent but Lazy 921 | 922 | 923 | ``` 924 | 925 | Pretty much this files is much like other files and it performs a switch with WIP fibers in the stack and change/replace them as needed. 926 | 927 | **ReactFiberHostContext.js** 928 | 929 | 930 | This files is very similar but the main difference I'm seeing is that it creates a container for the Host root. 931 | 932 | Note: Saw this in Reconciler.js 933 | 934 | ``` 935 | 936 | Initiliaze the cursors for the Stack 937 | 938 | requiredContext(c, Value) 939 | return c 940 | 941 | getRootHostContainer > gets the requiredContext for the rootInstance 942 | 943 | pushHostContainer(fiber, nextRootInstance) 944 | // Push current root instance onto the stack; 945 | push rootCursor, > push contextCursor > push NO_CONTEXT 946 | get HostContext > pop context > push nextRootContext 947 | 948 | popHostContainer > pop cursors x 3 949 | 950 | getHostContext > using requiredContext get current cursor context 951 | 952 | pushHostContext(fiber) > double push on to the stack, the current and next 953 | 954 | // Do not pop unless this Fiber provided the current context. 955 | // pushHostContext() only pushes Fibers that provide unique contexts. 956 | 957 | popHostContext(fiber) > double pop on the stack and fiber 958 | 959 | ``` 960 | ----------- 961 | ### How The Work is Handled. 962 | 963 | **READ BELOW ONLY IF YOU HAVE READ THE ABOVE, as these work files will easily make sense if the inner parts are understood.** 964 | 965 | This file mostly as stated by the name begins the work on various parts. 966 | 967 | ``` 968 | 969 | reconcileChildren(current, workInProgress, nextChildren, renderExpirationTime) 970 | if there is no work being done on the current 971 | mountChildFibers(workInProgress, null, nextChildren, renderExpirationTime) 972 | else reconcileChildFibers 973 | 974 | forceUnmountCurrentAndReconcile(current, workInProgress, nextChildren, renderExpirationTime) 975 | on the workInProgress.child run reconcileChildFiber x2 976 | 977 | updateForwardRef(current, workInProgress, type, nextProps, renderExpirationTime) 978 | if it doesn't have a hasLegacyContextChanged and memoizedProps=nextProps 979 | if ref = current then bailOut 980 | 981 | reconcileChildren 982 | memoizProps and return the WIP.child 983 | 984 | updatePureComponent(current, workInProgress, Component, nextProps, updateExpirationTime, renderExpirationTime) 985 | an if bailoutconditions 986 | 987 | prepareToReadContext > reconcileChildren > memoizProps > return WIP.child 988 | 989 | updateFragment(current, workInProgress, renderExpirationTime) 990 | same as pure but remember that fragments and the lack of context 991 | 992 | updateMode(current, workInProgress, renderExpirationTime) 993 | same but for pendingProps.Children 994 | 995 | updateFunctionComponent(current, workInProgress, Component, nextProps, renderExpirationTime) 996 | same as but getUnmaskedContext and getMaskedContext before reconciling 997 | 998 | updateClassComponent(current, workInProgress, Component, nextProps, renderExpirationTime) 999 | 1000 | prepareToreadContext 1001 | if the current WIP.stateNode is null(not being worked on) 1002 | then constructClassInstance and mountClassInstance 1003 | else resumeMountClassInstance 1004 | else updateClassInstance since it's already being worked on 1005 | 1006 | return finishClassComponent 1007 | 1008 | finishClassComponent(current, workInProgress, Component, shouldUpdate, hasContext, renderExpirationTime) 1009 | markRef for the current WIP 1010 | an if bail condition 1011 | 1012 | let nextChildren be null if there is no nextChildren, otherwise render the nextChild 1013 | 1014 | if the current isn't null then forceUnmountCurrentandReconcile otherwise reconcileChildren 1015 | 1016 | memoizeState and Props > return WIP.child 1017 | 1018 | pushHostRootContext(workInProgress) 1019 | if it has a pendingContext then pushTopLevelContextObject with a true value, otherwise if the root has only context then set it to false 1020 | pushHostContainer 1021 | 1022 | updateHostRoot(current, workInProgress, renderExpirationTime) 1023 | pushHostRootContext 1024 | assign variables to WIP.props/state and processUpdateQueue 1025 | if nextChildren = prevChildren then restHydrationState and bail out 1026 | otherwise hydrate it > mountChildFibers 1027 | and ofcourse reconcileChildren > return WIP.child 1028 | 1029 | updateHostComponent(current, workInProgress, renderExpirationTime) 1030 | pushHostContext 1031 | assign WIP.type/pendingProps/memoizedProps 1032 | markRef > Deprioritize the subtree if needed > reconcile > memoizeProps > return WIP.child 1033 | 1034 | updateHostText > just updater for above host functions 1035 | 1036 | resolveDefaultProps(Component, baseProps) 1037 | using object.assign and a for let in loop resolve the props taken from ReactElement 1038 | return baseProps 1039 | 1040 | mountIndeterminateComponent(current, workInProgress, Component, updateExpirationTime, renderExpirationTime) 1041 | const props = workInProgress.pendingProps 1042 | if it's 'object' and !==null, and 'function' 1043 | cancelWorkTimer > readLazyComponentType > startWorkTimer 1044 | switch (resolvedTag) 1045 | FunctionComponentLazy > updateFunctionComponent 1046 | ClassComponentLazy > updateClassComponent 1047 | ForwardRefLazy > updateForwardRef 1048 | PureComponentlazy > updatePureComponent 1049 | return WIP.props > child 1050 | 1051 | assign variables and get unmasked and masked contexts > prepareToReadContext 1052 | if value is an 'object' and !null and value.render(function and undefined) 1053 | then get DerivedStateFromProps > applyDerivedStateFromProps if needed 1054 | adoptClassInstance > mountClassInstance > return finishClassComponent 1055 | otherwise reconcile > memoizProps > return WIP.child 1056 | 1057 | updateSuspenseComponent(current, workInProgress, renderExpirationTime) 1058 | check if it's currently being worked on the component has timed out 1059 | if it's being worked on the forceUnmountCurrentAndReconcile else reconcileChildren 1060 | WIP.memoized Props/State to the nextProps and DidTimeout > return WIP.child 1061 | 1062 | updatePortalComponent(current, workInProgress, renderExpirationTime) 1063 | pushHostContainer 1064 | if it isn't being worked then assign and reconcileChildFibers, else is same and > memoize > return 1065 | 1066 | updateContextProvider 1067 | same as above function but newProps/oldProps = memoized 1068 | pushProvider(workInProgress, newValue) 1069 | 1070 | if oldProps are present, bail out otherwise > propagateContextChange 1071 | reconcile 1072 | 1073 | updateContextConsumer 1074 | prepareToReadContext > assign new values > reconcile > return WIP.child 1075 | 1076 | // beginWork just uses the above function depending on the WIP.tag 1077 | // Keep the expiration Time in mind as well 1078 | // remember that purecomponents/lazy components have thenable functions, and just follows the same methods for pretty much all the cases. 1079 | 1080 | ``` 1081 | 1082 | 1083 | ### Complete Work 1084 | 1085 | This file literally just completes what was started by BeginWork. The beginwork sets the pieces in motion and then CompleteWork acts on the pieces. 1086 | 1087 | **markUpdate** and **markRef** just tag the fibers. 1088 | 1089 | **appendAllChildren** runs a while loop on the childrens until all the siblings are accounted for. There is an inner function called **appendInitialChild** which sets the loop in motion. 1090 | 1091 | ``` 1092 | 1093 | if the work is mutable then 1094 | updateHostContainer 1095 | updateHostComponent > get the context > prepare the update > if it has a payload > markUpdate 1096 | updateHostText > markUpdate 1097 | elif persists 1098 | appendAllChildrenToContainer(containerChildSet, workInProgress) > run a while loop on child and siblings 1099 | updateHostContainer > create the container > append > mark > finalizeContainerChildren with the newChildSet 1100 | updateHostComponent > switch old props with memoized and create a new instance > get the host context > prepareUpdate > clone > finalizeInitialChildren > markUpdate 1101 | updateHostText > if old !== newText then get host container, context > create an instance > markUpdate 1102 | else literally do nothing 1103 | 1104 | // The Main Function 1105 | 1106 | completeWork(current, workInProgress, renderExpirationTime) 1107 | switch depending on Tag 1108 | all the functions are same for the Work Tags with little variation 1109 | pop From Container 1110 | Update The Container 1111 | 1112 | // Remember not all functions have these container...pure/fragments 1113 | 1114 | ``` 1115 | 1116 | There is no need to go over the entire **ReactFiberUnwindWork** when all it's doing is just setting the values(container/whatever was acted upon in beginwork) to null by popping them from where they were put in and gets ready for the next rinse and repeat 1117 | There is also functions for throwing error for various conditions based on the catch error boundary which is part of the new api in React 1118 | I will explain this in detail when I get the chance but if you have made it this far then you should automatically know what should happen here. 1119 | 1120 | 1121 | ------------ 1122 | CommitWork 1123 | ------------ 1124 | 1125 | 1126 | **ReactFiberReconciler.js** 1127 | 1128 | This file is in charge of creating the container, and scheduling the whole Fiber Update process. 1129 | 1130 | ``` 1131 | 1132 | // 0 is PROD, 1 is DEV For BundleType 1133 | 1134 | getContextForSubtree(parentComponent) 1135 | if null then return emptyContextObject 1136 | 1137 | Get the map of the fiber instance, and findCurrentUnmaskedContext(fiber) 1138 | if the fibers tag is ClassComponent then processChildContext, if it's lazy do the whole thenable and process context 1139 | 1140 | return parentContext 1141 | 1142 | scheduleRootUpdate(current, element, expirationTime, callback) 1143 | create > enqueue > scheduleWork > return expirationTime 1144 | 1145 | 1146 | updateContainerAtExpirationTime(element, container, parentComponent, expirationTime, callback) 1147 | get the current container, and contextForSubTree 1148 | if context is null then return context else set it to pending 1149 | return schedule RootUpdate 1150 | 1151 | findHostInstance(component) 1152 | get the hostFiber using findCurrentHostFiber and return it's stateNode 1153 | 1154 | createContainer(containerInfo, isConcurrent, hydrate) > return the created Fiber Root 1155 | 1156 | updateContainer(element, container, parentComponent, callback) 1157 | on the current container > computeExpirationForFiber and return updateContainerAtExpirationTime 1158 | 1159 | getPublicRootInstance(container) 1160 | get the current container and depending on it's HostComponent get it's public Instance, which is the child.stateNode 1161 | 1162 | 1163 | ``` 1164 | 1165 | it's creating a container for the current component/whatever(get Fibers and all for the root) 1166 | 1167 | 1168 | and then scheduling an update at Root, 1169 | 1170 | at this stage setState should trigger, which ends up scheduling the work assigning expirationTimes. 1171 | 1172 | Scheduling The Work will start the process of 1173 | 1174 | ``` 1175 | Begin > CompleteWork > UnWind > Commit 1176 | 1177 | Where it does work such as Reconciling Child Fibers, context, and etc. 1178 | 1179 | ``` 1180 | 1181 | Update the component/whatever, rinse and repeat 1182 | 1183 | 1184 | 1185 | --------------- 1186 | 1187 | **Scheduler.js** 1188 | 1189 | Check Out This Repo 1190 | 1191 | https://github.com/spanicker/main-thread-scheduling 1192 | 1193 | ---------------- 1194 | ## REACT-DOM AND HOW IT WORKS 1195 | 1196 | --------------------- 1197 | 1198 | ### Something You May Have Realized. If You Actually Went Through The Files..... 1199 | 1200 | 1201 | 1202 | 1203 | --------------------------------------------------------------------------------