Algorithms in React

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=47 koba04
September 07, 2018

Algorithms in React

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=128

koba04

September 07, 2018
Tweet

Transcript

  1. "MHPSJUINTJO3FBDU !LPCB CVJMEFSTDPO4FQ 

  2. LPCB

  3. We Are Hiring!!

  4. 8IBU*UBMLBCPVU • Why does React replace its implementation from Stack

    to Fiber? • The concept of Fiber • How to solve the problems • Algorithms that Fiber uses
  5. 8IBUJT3FBDU

  6. https://reactjs.org

  7. const LikeApp = props => <main> <Heading>Like App!</Heading> <p>{props.likeCount}</p>
 <button

    onClick={props.like}>like</button> </main>; let likeCount = 0; const update = () => ++likeCount; const render = () => ReactDOM.render( <LikeApp likeCount={likeCount} like={() => update() && render()} />, document.getElementById(‘app’) ); render(); ←only update the UI ←render the whole App
  8. -FUnTEJWFJOUP3FBDU JOUFSOBMT

  9. None
  10. "SDIJUFDUVSFPG3FBDU

  11. "SDIJUFDUVSFPG3FBDU <div /> <App /> <View /> <Text /> Stack

    Fiber DOM(Stack) Native(Stack) Canvas etc Component Reconciler Renderer HTMLElement SVGElement UIView Host CanvasElement Native(Fiber) DOM(Fiber) Fiber(Wasm?)
  12. 4UBDL3FDPODJMFS

  13. 4UBDL3FDPODJMFS • ʙv15 • Traverse and process a ReactElement tree

    recursively • Diff and Patch are the same time during traversing ReactElement tree • Process synchronously • Create a public instance and an internal instance
  14. 3FBDU&MFNFOU • ReactElement is an object represents the corresponding DOM

    • Recreate each calling the render function • Lightweight • No state
  15. 1VCMJD*OTUBODF • An instance of Composite Component that you create

    with React.Component • The lifecycle is mounting ʙ unmounting
  16. *OUFSOBM*OTUBODF • An instance that React uses internally • The

    lifecycle is mounting ʙ unmounting • Stack traverses a React Component tree through the internal instance • ReactInstanceMap.set(publicInstance, internalInstance);
  17. 3FBDU*OTUBODF.BQ var ReactInstanceMap = { remove: function(key) { key._reactInternalInstance =

    undefined; }, get: function(key) { return key._reactInternalInstance; }, has: function(key) { return key._reactInternalInstance !== undefined; }, set: function(key, value) { key._reactInternalInstance = value; }, }; /15-stable/src/renderers/shared/shared/ReactInstanceMap.js
  18. ReactElement InternalInstance ReactElement ReactElement PublicInstance Mount Unmount

  19. Reconciler.mountComponent InternalInstance.mountComponent Reconciler.mountComponent InternalInstance.mountComponent Reconciler.mountComponent InternalInstance.mountComponent … child child child

    recursive
  20. None
  21. 8IBUJTUIFQSPCMFN

  22. 5IFQSPCMFNTPG4UBDL • Stack must process a ReactElement tree synchronously because

    it traverse the tree with recursive calls • It leads blocking the UI thread with a big ReactElement tree • It takes time to traverse the tree even though you have only small updates • In order to avoid that, you have to implement shouldComponentUpdate in your components
  23. Sync

  24. 'JCFS3FDPODJMFS

  25. 'JCFS3FDPODJMFS • Traverse and process a ReactElement tree through Fiber

    objects • Render Phase and Commit Phase • Fiber can suspend and resume in its Render Phase
  26. 'JCFS • Fiber is an unit of work • A

    ReactElement tree becomes A Linked list of Fibers • Fiber is a fixed data structure, which can share the same hidden class • A Fiber has an alternate Fiber • Double Buffer (workInProgress <-> current)
  27. https://en.wikipedia.org/wiki/Fiber_(computer_science)

  28. type Fiber = {| tag: WorkTag, type: any, stateNode: any,

    return: Fiber | null, child: Fiber | null, sibling: Fiber | null, pendingProps: any, memoizedProps: any, memoizedState: any, updateQueue: UpdateQueue<any> | null, mode: TypeOfMode, effectTag: TypeOfSideEffect, nextEffect: Fiber | null, expirationTime: ExpirationTime, alternate: Fiber | null, : |} /packages/react-reconciler/src/ReactFiber.js
  29. -JOLFE-JTU • A ReactElement tree becomes A Linked list of

    Fibers • Fiber processes sequentially, doesn’t need random access • Search … O(n) • Insert/Delete … O(1)
  30. https://en.wikipedia.org/wiki/Linked_list

  31. <App> <UserList> <User name=“koba01” /> <User name=“koba02” /> </UserList> </App>

    HostRoot App child parent UserList child parent User child parent User sibling koba01 koba02 child parent parent child parent
  32. child parent child parent Insert child parent Delete child child

    parent parent
  33. let root = fiber; let node = fiber; while (true)

    { if (node.child && !hasProcessed(node.child)) { node = node.child; continue; } if (node === root) { return; } while (!node.sibling) { if (!node.parent) { return; } node = node.parent;
 } node = node.sibling; } Search the children Reach the root Return to the root Reach the root Search the siblings
  34. HostRoot App UserList User User koba01 koba02 child child child

    parent sibling child parent parent parent child parent
  35. )PXUPBDIJFWF BTZODISPOPVTSFOEFSJOH

  36. %PVCMF#VGGFS HostRoot App child parent section child parent … alternate

    HostRoot App child parent section child parent … Current Work in Progress alternate alternate alternate
  37. %PVCMF#VGGFS HostRoot App child parent section child parent … alternate

    HostRoot App child parent section child parent … (Work In Progress) Current alternate alternate alternate Commit!
  38. https://www.gameprogrammingpatterns.com/double-buffer.html

  39. 3FOEFS1IBTF BOE $PNNJU1IBTF

  40. FiberA FiberB FiberC FiberD FiberE IdleTime IdleTime IdleTime Render Phase

    (Async) Commit Phase (Sync) SideEffect SideEffect SideEffect Commit SideEffects
  41. 3FOEFS1IBTF $PNNJU1IBTF • Prepare for updating • Render Phase can

    be async • Extract Side Effects • No Side Effects • Interruptible • Apply Side Effects that affect to the Host • Commit Phase must be sync • Call some lifecycle methods • Consistency
  42. let nextUnitOfWork = findNextUnitOfWork(); const workLoop = () => {

    while (nextUnitOfWork !== null) { const workInProgress = nextUnitOfWork; nextUnitOfWork = beginWork(workInProgress); if (nextUnitOfWork === null) { nextUnitOfWork = completeWork(workInProgress); ɹɹ } if (!hasRemainingTime()) {
 scheduleUpdate(workLoop); return; } } commitWork(); }
  43. HostRoot App child parent UserList child parent User child parent

    User sibling koba01 koba02 child parent parent child parent beginWork completeWork
  44. Async

  45. 8IBUJTUIF4JEF&GGFDU

  46. 4JEF&GGFDU • Side Effect is set to Fiber at Render

    Phase • Side Effect is processed at Commit Phase • Because it must process at the right time • Fiber has nextEffect property as the pointer to apply Side Effects by the right order
  47. export const NoEffect = 0b00000000000; export const PerformedWork = 0b00000000001;

    export const Placement = 0b00000000010; export const Update = 0b00000000100; export const PlacementAndUpdate = 0b00000000110; export const Deletion = 0b00000001000; export const ContentReset = 0b00000010000; export const Callback = 0b00000100000; export const DidCapture = 0b00001000000; export const Ref = 0b00010000000; export const Snapshot = 0b00100000000; export const Incomplete = 0b01000000000; export const ShouldCapture = 0b10000000000; /packages/shared/ReactSideEffectTags.js
  48. <App> <UserList> <User name=“koba01” /> <User name=“koba02” /> </UserList> </App>

    HostRoot App UserList User User koba01 koba04 <App> <UserList> <User name=“koba01” /> <User name=“koba04” /> </UserList> </App> nextEffect nextEffect nextEffect firstEffect PureComponent
  49. )PXUPNBOBHF NVMUJQMFVQEBUFT 

  50. 6QEBUF2VFVF • Update Queue is a Linked list • Update

    Queue is stored in a Fiber that invoke the update • Double Buffering to interrupt the other updates • workInProgress 㲗 current • You can imagine Git rebase as a metaphor
  51. type Update<State> = { expirationTime: ExpirationTime, tag: 0 | 1

    | 2 | 3, payload: any, callback: (() => mixed) | null, next: Update<State> | null, nextEffect: Update<State> | null, }; type UpdateQueue<State> = { baseState: State, firstUpdate: Update<State> | null, lastUpdate: Update<State> | null, firstEffect: Update<State> | null, lastEffect: Update<State> | null, : } /packages/react-reconciler/src/ReactUpdateQueue.js
  52. if (queue.lastUpdate === null) {
 queue.firstUpdate = queue.lastUpdate = update;

    } else { queue.lastUpdate.next = update; queue.lastUpdate = update; } Append a update to the Update Queue /packages/react-reconciler/src/ReactUpdateQueue.js
  53. class App extends React.Component { constructor(props) { this.state = {

    count: 1 } } render() { return ( <section> <p>{this.state.count}</p> <button onClick={() => this.setState(state => ({ count: state.count + 1}))} > ++ </button> </section> ); } }
  54. HostRoot App child parent section child parent … alternate HostRoot

    App child parent section child parent … alternate alternate alternate Update Queue Update Queue
  55. A Current Queue WorkInProgress Queue B C D C D

    Commit A Current Queue WorkInProgress Queue B C D C D
  56. A Current Queue WorkInProgress Queue B C D C D

    Abort Current Queue WorkInProgress Queue A B C D A B C D
  57. A(High) Update Queue 1st Update B(Low) C(High) D(Low) A(High) C(High)

    2nd Update B(Low) C(High) D(Low) A(High) Rebase baseState
  58. this.setState({ count: this.state.count + 1 }); this.setState(state => ({ count:

    state.count + 1 }));
  59. )PXUPQSJPSJUJ[F UIFVQEBUFT

  60. &YQJSBUJPO5JNF • React had used priority levels to prioritize the

    updates • The approach of priority levels has a Starvation Problem • In order to solve this, React uses Expiration Time • If an expiration time has come, the update processes synchronously
  61. https://en.wikipedia.org/wiki/Starvation_(computer_science)

  62. export type PriorityLevel = 0 | 1 | 2 |

    3 | 4 | 5; module.exports = { NoWork: 0, SynchronousPriority: 1, AnimationPriority: 2, HighPriority: 3, LowPriority: 4, OffscreenPriority: 5, }; /15-stable/src/renderers/shared/fiber/ReactPriorityLevel.js
  63. export const NoWork = 0; export const Sync = 1;

    export const Never = MAX_SIGNED_31_BIT_INT; const UNIT_SIZE = 10; const MAGIC_NUMBER_OFFSET = 2; function ceiling(num: number, precision: number): number { return (((num / precision) | 0) + 1) * precision; } function computeExpirationBucket(currentTime, expirationInMs, bucketSizeMs): ExpirationTime { return ( MAGIC_NUMBER_OFFSET + ceiling( currentTime - MAGIC_NUMBER_OFFSET + expirationInMs / UNIT_SIZE, bucketSizeMs / UNIT_SIZE, ) ); } export const LOW_PRIORITY_EXPIRATION = 5000; export const LOW_PRIORITY_BATCH_SIZE = 250; export const HIGH_PRIORITY_EXPIRATION = __DEV__ ? 500 : 150; export const HIGH_PRIORITY_BATCH_SIZE = 100 /packages/react-reconciler/src/ReactFiberExpirationTime.js
  64. 100 90 120 50 100 90 120 100 120 1

    Sync 1st 2nd 3rd
  65. *OUFSBDUJWF&WFOU5ZQF • React treats some event types as interactive events

    • Updates in interactive events are prioritized as High Priorities • Interactive Events … • blur, click, dragStart, drop, focus, input, keyDown, mouseDown, play, pause, submit, touchStart… • /packages/react-dom/src/events/SimpleEventPlugin.js
  66. <input type=“text” value={inputText} onChange={({ target: { value } }) =>

    { this.setState({ inputText: value }); // High Priority requestAnimationFrame(() => { this.setState({ filterValue: value }); // Low Priority }); }} />
  67. %FNP https://github.com/koba04/react-timeslicing-demo

  68. #JUXJTF0QFSBUPST

  69. #JUXJTF0QFSBUPST • Fiber uses Bitwise operations in many places internally

    • Mode • Effect • Context API - ObservedBits • (Simple Cache Provider - CalculateChangedBits)
  70. export const PerformedWork = 0b00000000001; export const Placement = 0b00000000010;

    export const Update = 0b00000000100; export const PlacementAndUpdate = 0b00000000110; export const Deletion = 0b00000001000; export const ContentReset = 0b00000010000; export const Callback = 0b00000100000; export const DidCapture = 0b00001000000; export const Ref = 0b00010000000; export const Snapshot = 0b00100000000; export const LifecycleEffectMask = 0b00110100100; export const HostEffectMask = 0b00111111111; /packages/shared/ReactSideEffectTags.js
  71. // Render Phase (/packages/react-reconciler/src/ReactFiberCompleteWork.js) next.effectTag &= HostEffectMask; sourceFiber.effectTag &= ~LifecycleEffectMask;

    function markUpdate(workInProgress: Fiber) { workInPregress.effectTag |= Update; } // CommitPhase (/packages/react-reconciler/src/ReactFiberCommitWork.js) if (finishedWork.effectTag & Update) { if (current === null) { : instance.componentDidMount(); } else { : } }
  72. // Mode export const NoContext = 0b000; export const AsyncMode

    = 0b001; export const StrictMode = 0b010; export const ProfileMode = 0b100; if (workInProgress.mode & StrictMode) { // do something } const pingTime = (workInProgress.mode & AsyncMode) === NoEffect ? Sync : renderExpirationTime; /packages/react-reconciler/src/ReactTypeOfMode.js
  73. const observedBits = { foo: 0b01, bar: 0b10 }; const

    { Consumer } = React.createContext(store, (prev, next) => { let result = 0; if (prev.foo !== next.foo) result |= observedBits.foo; if (prev.bar !== next.bar) result |= observedBits.bar; return result; }); const Foo = () => <Consumer unstable_observedBits={observedBits.foo}> {({foo}) => …} </Consumer> const Bar = () => <Consumer unstable_observedBits={observedBits.bar}> {({bar}) => …} </Consumer>
  74. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Examples

  75. 4VTQFOTF

  76. )PXUPNBOBHFBTZOD EFQFOEFODJFT

  77. &SSPS#PVOEBSJFT HostRoot App UserList User Text Throw Error!!! ErrorPage Error

    Error componentDidCatch
  78. 4VTQFOTF HostRoot App UserList User Text Throw Promise!!! Promise React.

    Placeholder If Promise has not yet been resolved -> Suspend has been resolved -> Resume the rendering had not resolved -> Display Fallback Component Fallback
  79. const App = () => ( <React.Placeholder delayMs={1000} fallback={<Loading />}>

    <EntrySection /> </React.Placeholder> ); const EntrySection = () => <Entry />; const Entry = () => { // throw Promise if a cache doesn’t have an entry data const entry = fetchEntryWithCache(); return <p>{entry.data}</p>; };
  80. How to handle tryʙcatch in async reconciliation?

  81. 4VTQFOTF HostRoot App UserList User Text Throw Promise!!! React. Placeholder

    Fallback parent parent Fiber can trace the component stack through the Linked List structures
  82. %FNP https://github.com/koba04/react-suspense-demo

  83. 4VTQFOTF • Suspense can suspend a rendering by throwing a

    Promise • tryʙcatch can’t catch thrown objects in Async Rendering • Fiber can trace the component stack through Linked List • Still in development
  84. 3FDBQ • React has replaced its implementation from Stack to

    Fiber to solve the problems. • Fiber makes async and flexible scheduling possible • Fiber uses many technics internally like LinkedList, Double Buffer, Expiration Time, Bitwise Operators etc.
  85. 5IBOLZPV TQFBLFSEFDLDPNLPCB