Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Algorithms in React

koba04
September 07, 2018

Algorithms in React

koba04

September 07, 2018
Tweet

More Decks by koba04

Other Decks in Programming

Transcript

  1. 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
  2. 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
  3. "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?)
  4. 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
  5. 3FBDU&MFNFOU • ReactElement is an object represents the corresponding DOM

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

    with React.Component • The lifecycle is mounting ʙ unmounting
  7. *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);
  8. 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
  9. 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
  10. 'JCFS3FDPODJMFS • Traverse and process a ReactElement tree through Fiber

    objects • Render Phase and Commit Phase • Fiber can suspend and resume in its Render Phase
  11. '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)
  12. 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
  13. -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)
  14. <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
  15. 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
  16. HostRoot App UserList User User koba01 koba02 child child child

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

    HostRoot App child parent section child parent … Current Work in Progress alternate alternate alternate
  18. %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!
  19. FiberA FiberB FiberC FiberD FiberE IdleTime IdleTime IdleTime Render Phase

    (Async) Commit Phase (Sync) SideEffect SideEffect SideEffect Commit SideEffects
  20. 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
  21. 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(); }
  22. HostRoot App child parent UserList child parent User child parent

    User sibling koba01 koba02 child parent parent child parent beginWork completeWork
  23. 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
  24. 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
  25. <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
  26. 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
  27. 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
  28. 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
  29. 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> ); } }
  30. HostRoot App child parent section child parent … alternate HostRoot

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

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

    Abort Current Queue WorkInProgress Queue A B C D A B C D
  33. 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
  34. &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
  35. 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
  36. 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
  37. *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
  38. <input type=“text” value={inputText} onChange={({ target: { value } }) =>

    { this.setState({ inputText: value }); // High Priority requestAnimationFrame(() => { this.setState({ filterValue: value }); // Low Priority }); }} />
  39. #JUXJTF0QFSBUPST • Fiber uses Bitwise operations in many places internally

    • Mode • Effect • Context API - ObservedBits • (Simple Cache Provider - CalculateChangedBits)
  40. 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
  41. // 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 { : } }
  42. // 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
  43. 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>
  44. 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
  45. 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>; };
  46. 4VTQFOTF HostRoot App UserList User Text Throw Promise!!! React. Placeholder

    Fallback parent parent Fiber can trace the component stack through the Linked List structures
  47. 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
  48. 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.