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. "MHPSJUINTJO3FBDU
    !LPCB
    CVJMEFSTDPO4FQ

    View Slide

  2. LPCB

    View Slide

  3. We Are Hiring!!

    View Slide

  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

    View Slide

  5. 8IBUJT3FBDU

    View Slide

  6. https://reactjs.org

    View Slide

  7. const LikeApp = props =>

    Like App!
    {props.likeCount}

    like
    ;
    let likeCount = 0;
    const update = () => ++likeCount;
    const render = () =>
    ReactDOM.render(
    likeCount={likeCount}
    like={() => update() && render()}
    />,
    document.getElementById(‘app’)
    );
    render();
    ←only update the UI
    ←render the whole App

    View Slide

  8. -FUnTEJWFJOUP3FBDU
    JOUFSOBMT

    View Slide

  9. View Slide

  10. "SDIJUFDUVSFPG3FBDU

    View Slide

  11. "SDIJUFDUVSFPG3FBDU

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

    View Slide

  12. 4UBDL3FDPODJMFS

    View Slide

  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

    View Slide

  14. 3FBDU&MFNFOU
    • ReactElement is an object represents the corresponding DOM
    • Recreate each calling the render function
    • Lightweight
    • No state

    View Slide

  15. 1VCMJD*OTUBODF
    • An instance of Composite Component that you create with
    React.Component
    • The lifecycle is mounting ʙ unmounting

    View Slide

  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);

    View Slide

  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

    View Slide

  18. ReactElement
    InternalInstance
    ReactElement ReactElement
    PublicInstance
    Mount Unmount

    View Slide

  19. Reconciler.mountComponent
    InternalInstance.mountComponent
    Reconciler.mountComponent
    InternalInstance.mountComponent
    Reconciler.mountComponent
    InternalInstance.mountComponent

    child
    child
    child
    recursive

    View Slide

  20. View Slide

  21. 8IBUJTUIFQSPCMFN

    View Slide

  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

    View Slide

  23. Sync

    View Slide

  24. 'JCFS3FDPODJMFS

    View Slide

  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

    View Slide

  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)

    View Slide

  27. https://en.wikipedia.org/wiki/Fiber_(computer_science)

    View Slide

  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 | null,
    mode: TypeOfMode,
    effectTag: TypeOfSideEffect,
    nextEffect: Fiber | null,
    expirationTime: ExpirationTime,
    alternate: Fiber | null,
    :
    |}
    /packages/react-reconciler/src/ReactFiber.js

    View Slide

  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)

    View Slide

  30. https://en.wikipedia.org/wiki/Linked_list

    View Slide







  31. HostRoot
    App
    child
    parent
    UserList
    child
    parent
    User
    child
    parent
    User
    sibling
    koba01 koba02
    child
    parent
    parent
    child
    parent

    View Slide

  32. child
    parent
    child
    parent
    Insert
    child
    parent
    Delete
    child child
    parent
    parent

    View Slide

  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

    View Slide

  34. HostRoot
    App
    UserList
    User User
    koba01 koba02
    child
    child
    child parent
    sibling
    child parent
    parent
    parent
    child parent

    View Slide

  35. )PXUPBDIJFWF
    BTZODISPOPVTSFOEFSJOH

    View Slide

  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

    View Slide

  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!

    View Slide

  38. https://www.gameprogrammingpatterns.com/double-buffer.html

    View Slide

  39. 3FOEFS1IBTF
    BOE
    $PNNJU1IBTF

    View Slide

  40. FiberA FiberB FiberC FiberD FiberE
    IdleTime IdleTime IdleTime
    Render Phase (Async) Commit Phase (Sync)
    SideEffect SideEffect SideEffect
    Commit SideEffects

    View Slide

  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

    View Slide

  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();
    }

    View Slide

  43. HostRoot
    App
    child
    parent
    UserList
    child
    parent
    User
    child
    parent
    User
    sibling
    koba01 koba02
    child
    parent
    parent
    child
    parent
    beginWork completeWork

    View Slide

  44. Async

    View Slide

  45. 8IBUJTUIF4JEF&GGFDU

    View Slide

  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

    View Slide

  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

    View Slide







  48. HostRoot
    App
    UserList
    User User
    koba01 koba04






    nextEffect
    nextEffect
    nextEffect
    firstEffect
    PureComponent

    View Slide

  49. )PXUPNBOBHF
    NVMUJQMFVQEBUFT

    View Slide

  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

    View Slide

  51. type Update = {
    expirationTime: ExpirationTime,
    tag: 0 | 1 | 2 | 3,
    payload: any,
    callback: (() => mixed) | null,
    next: Update | null,
    nextEffect: Update | null,
    };
    type UpdateQueue = {
    baseState: State,
    firstUpdate: Update | null,
    lastUpdate: Update | null,
    firstEffect: Update | null,
    lastEffect: Update | null,
    :
    }
    /packages/react-reconciler/src/ReactUpdateQueue.js

    View Slide

  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

    View Slide

  53. class App extends React.Component {
    constructor(props) {
    this.state = {
    count: 1
    }
    }
    render() {
    return (

    {this.state.count}
    onClick={() => this.setState(state => ({ count: state.count + 1}))}
    >
    ++


    );
    }
    }

    View Slide

  54. HostRoot
    App
    child
    parent
    section
    child
    parent

    alternate
    HostRoot
    App
    child
    parent
    section
    child
    parent

    alternate
    alternate
    alternate
    Update
    Queue
    Update
    Queue

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  58. this.setState({ count: this.state.count + 1 });
    this.setState(state => ({ count: state.count + 1 }));

    View Slide

  59. )PXUPQSJPSJUJ[F
    UIFVQEBUFT

    View Slide

  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

    View Slide

  61. https://en.wikipedia.org/wiki/Starvation_(computer_science)

    View Slide

  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

    View Slide

  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

    View Slide

  64. 100
    90
    120 50
    100
    90
    120
    100
    120 1
    Sync
    1st
    2nd
    3rd

    View Slide

  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

    View Slide

  66. type=“text”
    value={inputText}
    onChange={({ target: { value } }) => {
    this.setState({ inputText: value }); // High Priority
    requestAnimationFrame(() => {
    this.setState({ filterValue: value }); // Low Priority
    });
    }}
    />

    View Slide

  67. %FNP
    https://github.com/koba04/react-timeslicing-demo

    View Slide

  68. #JUXJTF0QFSBUPST

    View Slide

  69. #JUXJTF0QFSBUPST
    • Fiber uses Bitwise operations in many places internally
    • Mode
    • Effect
    • Context API - ObservedBits
    • (Simple Cache Provider - CalculateChangedBits)

    View Slide

  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

    View Slide

  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 {
    :
    }
    }

    View Slide

  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

    View Slide

  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 = () =>

    {({foo}) => …}

    const Bar = () =>

    {({bar}) => …}

    View Slide

  74. https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Bitwise_Operators#Examples

    View Slide

  75. 4VTQFOTF

    View Slide

  76. )PXUPNBOBHFBTZOD
    EFQFOEFODJFT

    View Slide

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

    View Slide

  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

    View Slide

  79. const App = () => (
    }>


    );
    const EntrySection = () => ;
    const Entry = () => {
    // throw Promise if a cache doesn’t have an entry data
    const entry = fetchEntryWithCache();
    return {entry.data};
    };

    View Slide

  80. How to handle tryʙcatch in async reconciliation?

    View Slide

  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

    View Slide

  82. %FNP
    https://github.com/koba04/react-suspense-demo

    View Slide

  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

    View Slide

  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.

    View Slide

  85. 5IBOLZPV
    TQFBLFSEFDLDPNLPCB

    View Slide