$30 off During Our Annual Pro Sale. View Details »

Make it Declarative with React

koba04
November 30, 2019

Make it Declarative with React

This is a presentation for JSConf JP!

https://jsconf.jp/2019/talk/toru-kobayashi

koba04

November 30, 2019
Tweet

More Decks by koba04

Other Decks in Programming

Transcript

  1. Make it Declarative with React
    @koba04 / JSConf JP 2019

    View Slide

  2. Toru Kobayashi
    @koba04
    Twitter / GitHub
    Web Developer
    2007

    Cybozu
    →Frontend Expert Team
    SmartHR
    →Frontend Advisor

    View Slide

  3. ReactVoice
    ReactVoice.
    .render
    render(
    (
    <
    <>
    >
    <
    kyoko>
    >
    こんにちは
    こんにちは
    <

    /kyoko
    kyoko>
    >
    <
    alex>
    >
    I
    I work
    work as
    as a frontend developer
    a frontend developer for
    for Cybozu and
    Cybozu and
    I
    I work
    work as
    as a frontend advisor
    a frontend advisor for
    for SmartHR
    SmartHR.
    .
    <

    /alex
    alex>
    >
    <
    alex>
    >My Twitter and GitHub accounts are @koba04
    My Twitter and GitHub accounts are @koba04<

    /alex
    alex>
    >
    <
    victoria>
    >
    I
    I'm also one
    'm also one of
    of the organizers
    the organizers of
    of React
    React.
    .js meetup
    js meetup in
    in Tokyo and
    Tokyo and
    a contributor
    a contributor of
    of React
    React.
    .
    <

    /victoria
    victoria>
    >
    <
    victoria>
    >I
    I've been working
    've been working with
    with React
    React for
    for 5
    5years
    years.
    .<

    /victoria
    victoria>
    >
    <

    />
    >,
    ,
    {
    {}
    }
    )
    );
    ;

    View Slide

  4. Agenda
    Benefits of Declarative Programming for UI
    Custom renderer of React
    Live Demo!

    View Slide

  5. Declarative Programming for UI

    View Slide

  6. Declarative Programming
    https://en.wikipedia.org/wiki/Declarative_programming
    In computer science, declarative programming is a programming paradigm—a
    style of building the structure and elements of computer programs—that expresses
    the logic of a computation without describing its control flow.
    Many languages that apply this style attempt to minimize or eliminate side effects
    by describing what the program must accomplish in terms of the problem
    domain, rather than describe how to accomplish it as a sequence of the
    programming language primitives

    View Slide

  7. Why Declarative?
    What Not How
    How -> Compiler
    Abstraction layer
    Optimization in the underlying layer
    Primitive as domain

    View Slide

  8. The logic of a computation without describing its control flow

    View Slide

  9. DOM manipulation is based on imperative
    operations

    View Slide

  10. Imperative
    const
    const view
    view =
    = document
    document.
    .querySelector
    querySelector(
    ('.view'
    '.view')
    );
    ;
    const
    const addButton
    addButton =
    = document
    document.
    .querySelector
    querySelector(
    ('.add-button'
    '.add-button')
    );
    ;
    // You have to implement how to update the view
    // You have to implement how to update the view
    addButton
    addButton.
    .addEventListener
    addEventListener(
    ('click'
    'click',
    , (
    ()
    ) =>
    => {
    {
    view
    view.
    .appendChild
    appendChild(
    (child
    child)
    )
    }
    })
    );
    ;

    View Slide

  11. Declarative
    const
    const view
    view =
    = document
    document.
    .querySelector
    querySelector(
    ('.view'
    '.view')
    );
    ;
    const
    const addButton
    addButton =
    = document
    document.
    .querySelector
    querySelector(
    ('.add-button'
    '.add-button')
    );
    ;
    const
    const state
    state =
    = [
    []
    ];
    ;
    addButton
    addButton.
    .addEventListener
    addEventListener(
    ('click'
    'click',
    , (
    ()
    ) =>
    => {
    {
    // update the state impleratively
    // update the state impleratively
    state
    state.
    .push
    push(
    (child
    child)
    );
    ;
    // describe the view declaratively based on the state
    // describe the view declaratively based on the state
    render
    render(
    (state
    state)
    );
    ;
    }
    })
    );
    ;
    // describing what the view should display
    // describing what the view should display
    const
    const render
    render =
    = state
    state =>
    => {
    {
    view
    view.
    .innerHTML
    innerHTML =
    = state
    state.
    .map
    map(
    (s
    s =>
    => `
    `
    ${
    ${s
    s}
    }
    `
    `)
    ).
    .join
    join(
    (''
    '')
    );
    ;
    }
    }
    Describing what to update the view

    View Slide

  12. = View(State)

    View Slide

  13. React updates views efficiently
    let
    let count
    count =
    = 1
    1;
    ;
    ReactDOM
    ReactDOM.
    .render
    render(
    (
    <
    div>
    >
    <
    Header /
    />
    >
    <
    p>
    >{
    {count
    count}
    }<

    /p
    p>
    >
    <

    /div
    div>
    >,
    ,
    container
    container
    )
    );
    ;
    count
    count =
    = 2
    2;
    ;
    ReactDOM
    ReactDOM.
    .render
    render(
    (
    <
    div>
    >
    <
    Header /
    />
    >
    <
    p>
    >{
    {count
    count}
    }<

    /p
    p>
    >
    <

    /div
    div>
    >,
    ,
    container
    container
    )
    )
    // p.textContent = 2; // React updates the DOM
    // p.textContent = 2; // React updates the DOM

    View Slide

  14. ReactDOM Renderer

    View Slide

  15. Describing what the program must accomplish in terms of the problem domain

    View Slide

  16. Abstract your application components
    DOM is an implementation detail
    React Component is a primitive of your domain.

    View Slide

  17. Build own domain layers with React
    const
    const view
    view =
    = document
    document.
    .querySelector
    querySelector(
    ('.view'
    '.view')
    );
    ;
    // describing what the view should display
    // describing what the view should display
    const
    const App
    App =
    = (
    ()
    ) =>
    => {
    {
    const
    const [
    [items
    items,
    , setItems
    setItems]
    ] =
    = useState
    useState(
    ([
    []
    ])
    );
    ;
    return
    return (
    (
    <
    Layout>
    >
    <
    Header>
    >title
    title<

    /Header
    Header>
    >
    <
    ItemList>
    >
    {
    {items
    items.
    .map
    map(
    (item
    item =>
    => <
    Item key=
    ={
    {item
    item.
    .id
    id}
    } item
    item=
    ={
    {item
    item}
    } /
    />
    >)
    )}
    }
    <

    /ItemList
    ItemList>
    >
    <
    AddItemButton
    onAddItem
    onAddItem=
    ={
    {item
    item =>
    => setItems
    setItems(
    (items
    items.
    .concat
    concat(
    (item
    item)
    ))
    )}
    }
    /
    />
    >
    <

    /Layout
    Layout>
    >
    )
    );
    ;
    }
    }
    ReactDOM
    ReactDOM.
    .render
    render(
    (<
    App /
    />
    >,
    , view
    view)
    );
    ;

    View Slide

  18. DOM as a Second-class Citizen
    Sebastian Markbåge - DOM as a Second-cl
    Sebastian Markbåge - DOM as a Second-cl…
    … 後で見る
    後で見る 共有
    共有
    Sebastian Markbåge / React Europe 2015

    View Slide

  19. React Custom Renderer

    View Slide

  20. Renderers

    View Slide

  21. Ink
    import
    import React
    React from
    from 'react'
    'react';
    ;
    import
    import {
    {render
    render,
    , Box
    Box,
    , Color
    Color}
    } from
    from 'ink'
    'ink';
    ;
    render
    render(
    (
    <
    Box>
    >
    <
    Color green>
    >Hello world
    Hello world!
    !<

    /Color
    Color>
    >
    <

    /Box
    Box>
    >
    )
    );
    ;

    View Slide

  22. ReactKonva
    ReactKonva
    ReactKonva.
    .render
    render(
    (
    <
    Stage width=
    ={
    {300
    300}
    } height
    height=
    ={
    {300
    300}
    }>
    >
    <
    Layer>
    >
    <
    Text text=
    ="Hello, world!"
    "Hello, world!" fontSize
    fontSize=
    ={
    {30
    30}
    } /
    />
    >
    <
    Star
    x
    x=
    ={
    {50
    50}
    }
    y
    y=
    ={
    {70
    70}
    }
    innerRadius
    innerRadius=
    ={
    {20
    20}
    }
    outerRadius
    outerRadius=
    ={
    {40
    40}
    }
    fill
    fill=
    ="tomato"
    "tomato"
    /
    />
    >
    <

    /Layer
    Layer>
    >
    <

    /Stage
    Stage>
    >,
    ,
    el
    el
    )
    );
    ;

    View Slide

  23. ReactThreeFiber
    import
    import React
    React,
    , {
    { useRef
    useRef }
    } from
    from 'react'
    'react'
    import
    import ReactDOM
    ReactDOM from
    from 'react-dom'
    'react-dom'
    import
    import {
    { Canvas
    Canvas,
    , useFrame
    useFrame }
    } from
    from 'react-three-fiber'
    'react-three-fiber'
    const
    const Cube
    Cube =
    = (
    ()
    ) =>
    => {
    {
    const
    const ref
    ref =
    = useRef
    useRef(
    ()
    )
    useFrame
    useFrame(
    ((
    ()
    ) =>
    => (
    (ref
    ref.
    .current
    current.
    .rotation
    rotation.
    .x
    x =
    = ref
    ref.
    .current
    current.
    .rotation
    rotation.
    .y
    y +=
    += 0.01
    0.01)
    ))
    )
    return
    return (
    (
    <
    mesh ref=
    ={
    {ref
    ref}
    }>
    >
    <
    boxBufferGeometry attach=
    ="geometry"
    "geometry" args
    args=
    ={
    {[
    [1
    1,
    , 1
    1,
    , 1
    1]
    ]}
    } /
    />
    >
    <
    meshNormalMaterial attach=
    ="material"
    "material" /
    />
    >
    <

    /mesh
    mesh>
    >
    )
    )
    }
    }
    ReactDOM
    ReactDOM.
    .render
    render(
    (<
    Canvas>
    ><
    Cube /
    />
    ><

    /Canvas
    Canvas>
    >,
    , el
    el)
    );
    ;

    View Slide

  24. ReactAST
    import
    import React
    React from
    from 'react'
    'react';
    ;
    import
    import {
    {
    renderAst
    renderAst,
    ,
    Code
    Code,
    ,
    ClassDeclaration
    ClassDeclaration,
    ,
    FunctionDeclaration
    FunctionDeclaration
    }
    } from
    from 'react-ast'
    'react-ast';
    ;
    const
    const ast
    ast =
    = renderAst
    renderAst(
    (
    <
    ClassDeclaration name=
    ="Hello"
    "Hello" superClassName
    superClassName=
    ="Array"
    "Array">
    >
    <
    Code>
    >const
    const hello
    hello =
    = 'world'
    'world'<

    /Code
    Code>
    >
    <
    FunctionDeclaration name=
    ="foo"
    "foo">
    >
    <
    Code>
    >return
    return 'bar'
    'bar'<

    /Code
    Code>
    >
    <

    /FunctionDeclaration
    FunctionDeclaration>
    >
    <

    /ClassDeclaration
    ClassDeclaration>
    >
    )
    );
    ;
    console
    console.
    .log
    log(
    (ast
    ast)
    );
    ;

    View Slide

  25. Building a Custom React DOM Renderer
    https://github.com/jquense/react-dom-lite
    https://conf.reactjs.org/event.html?sophiebits

    View Slide

  26. Architecture of React
    https://speakerdeck.com/koba04/algorithms-in-react

    View Slide

  27. react-reconciler
    npm install react-reconciler
    npm install react-reconciler
    packages/react-reconciler

    View Slide

  28. How to use
    import
    import Reconciler
    Reconciler from
    from "react-reconciler"
    "react-reconciler";
    ;
    const
    const renderer
    renderer =
    = Reconciler
    Reconciler(
    (hostconfig
    hostconfig)
    );
    ;
    export
    export const
    const YourReact
    YourReact =
    = {
    {
    render
    render(
    (
    element
    element:
    : React
    React.
    .ReactNode
    ReactNode,
    ,
    rootContainer
    rootContainer:
    : RootContainer
    RootContainer,
    ,
    callback
    callback =
    = (
    ()
    ) =>
    => {
    {}
    }
    )
    ) {
    {
    if
    if (
    (!
    !rootContainer
    rootContainer.
    .container
    container)
    ) {
    {
    rootContainer
    rootContainer.
    .container
    container =
    = {
    {}
    }
    rootContainer
    rootContainer.
    .container
    container.
    .fiberRoot
    fiberRoot =
    = renderer
    renderer.
    .createContainer
    createContainer(
    (
    container
    container,
    ,
    false
    false,
    ,
    false
    false
    )
    );
    ;
    }
    }
    renderer
    renderer.
    .updateContainer
    updateContainer(
    (element
    element,
    , container
    container.
    .fiberRoot
    fiberRoot,
    , null
    null,
    , callback
    callback)
    );
    ;
    }
    }
    }
    }

    View Slide

  29. HostConfig Interface #1
    Mutation(optional)
    getPublicInstance, getRootHostContext, getChildHostContext, prepareForCommit,
    resetAfterCommit, createInstance, appendInitialChild, finalizeInitialChildren,
    prepareUpdate, shouldSetTextContent, shouldDeprioritizeSubtree,
    createTextInstance scheduleDeferredCallback, cancelDeferredCallback,
    setTimeout, clearTimeout, noTimeout, now, isPrimaryRenderer supportsMutation,
    supportsPersistence, supportsHydration
    appendChild, appendChildToContainer, commitTextUpdate, commitMount,
    commitUpdate, insertBefore, insertInContainerBefore, removeChild,
    removeChildFromContainer, resetTextContent

    View Slide

  30. HostConfig Interface #2
    Persistence(optional)
    Hydration(optional)
    from @types/react-reconciler
    cloneInstance, createContainerChildSet, appendChildToContainerChildSet,
    finalizeContainerChildren, replaceContainerChildren
    canHydrateInstance, canHydrateTextInstance, getNextHydratableSibling,
    getFirstHydratableChild, hydrateInstance
    hydrateTextInstance,didNotMatchHydratedContainerTextInstance,
    didNotMatchHydratedTextInstance, didNotHydrateContainerInstance,
    didNotHydrateInstance,didNotFindHydratableContainerInstance,
    didNotFindHydratableContainerTextInstance, didNotFindHydratableInstance,
    didNotFindHydratableTextInstance

    View Slide

  31. View Slide

  32. HostConfig of renderers
    ReactDOM
    packages/react-dom/src/client/ReactDOMHostConfig.js
    ReactNative
    packages/react-native-renderer/src/ReactNativeHostConfig.js
    packages/react-native-renderer/src/ReactFabricHostConfig.js
    ReactTestRenderer
    packages/react-test-renderer/src/ReactTestHostConfig.js
    Ink
    vadimdemedes/ink/blob/master/src/reconciler.js
    ReactKonva
    konvajs/react-konva/blob/master/src/ReactKonvaHostConfig.js

    View Slide

  33. HostConfig?
    Side effects for a Host environment
    Define instances
    Define the mode for a renderer
    Hydration logic (if you need)

    View Slide

  34. Side effects for a Host environment
    ReactDOM
    ReactDOM.
    .render
    render(
    (
    <
    ul>
    >
    <
    li key=
    ="a"
    "a">
    >a
    a<

    /li
    li>
    >
    <
    li key=
    ="b"
    "b">
    >b
    b<

    /li
    li>
    >
    <
    li key=
    ="c"
    "c">
    >c
    c<

    /li
    li>
    >
    <

    /ul
    ul>
    >,
    ,
    container
    container
    )
    );
    ;
    ReactDOM
    ReactDOM.
    .render
    render(
    (
    <
    ul>
    >
    <
    li key=
    ="b"
    "b">
    >b
    b<

    /li
    li>
    >
    <
    li key=
    ="a"
    "a">
    >a
    a<

    /li
    li>
    >
    <
    li key=
    ="c"
    "c">
    >c
    c<

    /li
    li>
    >
    <

    /ul
    ul>
    >,
    ,
    container
    container
    )
    )
    // React update the DOM like the following
    // React update the DOM like the following
    // li.insertBefore(b, a);
    // li.insertBefore(b, a);

    View Slide

  35. Side effects for a Host environment
    export
    export function
    function insertBefore
    insertBefore(
    (
    parentInstance
    parentInstance:
    : Instance
    Instance,
    ,
    child
    child:
    : Instance
    Instance |
    | TextInstance
    TextInstance,
    ,
    beforeChild
    beforeChild:
    : Instance
    Instance |
    | TextInstance
    TextInstance
    )
    ):
    : void
    void {
    {
    // we have to remove a current instance at first
    // we have to remove a current instance at first
    const
    const index
    index =
    = parentInstance
    parentInstance.
    .children
    children.
    .indexOf
    indexOf(
    (child
    child)
    );
    ;
    if
    if (
    (index
    index !==
    !== -
    -1
    1)
    ) {
    {
    parentInstance
    parentInstance.
    .children
    children.
    .splice
    splice(
    (index
    index,
    , 1
    1)
    );
    ;
    }
    }
    // And then, we insert the instance into a new index
    // And then, we insert the instance into a new index
    const
    const beforeIndex
    beforeIndex =
    = parentInstance
    parentInstance.
    .children
    children.
    .indexOf
    indexOf(
    (beforeChild
    beforeChild)
    );
    ;
    parentInstance
    parentInstance.
    .children
    children.
    .splice
    splice(
    (beforeIndex
    beforeIndex,
    , 0
    0,
    , child
    child)
    );
    ;
    }
    }

    View Slide

  36. Others
    appendChild, appendInitialChild, appendChildToContainer
    commitTextUpdate, commitMount, commitUpdate
    insertBefore, insertInContainerBefore
    removeChild, removeChildFromContainer, resetTextContent

    View Slide

  37. createInstance, createTextInstance
    export
    export function
    function createInstance
    createInstance(
    (
    type
    type:
    : Type
    Type,
    ,
    props
    props:
    : Props
    Props,
    ,
    rootContainerInstance
    rootContainerInstance:
    : Container
    Container,
    ,
    hostContext
    hostContext:
    : HostContext
    HostContext,
    ,
    internalInstanceHandle
    internalInstanceHandle:
    : OpaqueHandle
    OpaqueHandle
    )
    ):
    : Instance
    Instance {
    {
    return
    return createYourHostInstance
    createYourHostInstance(
    (type
    type,
    , props
    props)
    );
    ;
    }
    }
    export
    export function
    function createTextInstance
    createTextInstance(
    (
    text
    text:
    : string
    string,
    ,
    rootContainerInstance
    rootContainerInstance:
    : Container
    Container,
    ,
    hostContext
    hostContext:
    : HostContext
    HostContext,
    ,
    internalInstanceHandle
    internalInstanceHandle:
    : OpaqueHandle
    OpaqueHandle
    )
    ):
    : TextInstance
    TextInstance {
    {
    return
    return createYourTextInstacne
    createYourTextInstacne(
    (text
    text)
    );
    ;
    }
    }

    View Slide

  38. getPublicInstance
    export
    export function
    function getPublicInstance
    getPublicInstance(
    (
    instance
    instance:
    : Instance
    Instance
    )
    ):
    : PublicInstance
    PublicInstance {
    {
    return
    return convertToPublicInstance
    convertToPublicInstance(
    (instance
    instance)
    );
    ;
    // react-dom
    // react-dom
    // return instance;
    // return instance;
    }
    }

    View Slide

  39. Define the mode for a renderer
    export
    export const
    const isPrimaryRenderer
    isPrimaryRenderer =
    = true
    true;
    ;
    export
    export const
    const supportsMutation
    supportsMutation =
    = true
    true;
    ;
    export
    export const
    const supportsPersistence
    supportsPersistence =
    = false
    false;
    ;
    export
    export const
    const supportsHydration
    supportsHydration =
    = false
    false;
    ;

    View Slide

  40. Type Definition for custom host config
    declare namespace
    declare namespace JSX
    JSX {
    {
    interface
    interface IntrinsicElements
    IntrinsicElements {
    {
    text
    text:
    : {
    {
    color
    color:
    : string
    string;
    ;
    children
    children?
    ?:
    : React
    React.
    .ReactNode
    ReactNode;
    ;
    }
    };
    ;
    }
    }
    }
    }
    https://www.typescriptlang.org/docs/handbook/jsx.html#intrinsic-elements

    View Slide

  41. Live Coding

    View Slide

  42. Thank you!!!
    speakerdeck.com/koba04/
    github.com/koba04/jsconf-jp-presentation

    View Slide