Make it Declarative with React

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=47 koba04
November 30, 2019

Make it Declarative with React

This is a presentation for JSConf JP!

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

45daf58c77e9dbbab5a1c8a5afc7ac5c?s=128

koba04

November 30, 2019
Tweet

Transcript

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

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

    Cybozu →Frontend Expert Team SmartHR →Frontend Advisor
  3. ReactVoice ReactVoice. .render render( ( < <> > < <kyoko

    kyoko> > こんにちは こんにちは < </ /kyoko kyoko> > < <alex 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 alex> >My Twitter and GitHub accounts are @koba04 My Twitter and GitHub accounts are @koba04< </ /alex alex> > < <victoria 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 victoria> >I I've been working 've been working with with React React for for 5 5years years. .< </ /victoria victoria> > < </ /> >, , { {} } ) ); ;
  4. Agenda Benefits of Declarative Programming for UI Custom renderer of

    React Live Demo!
  5. Declarative Programming for UI

  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
  7. Why Declarative? What Not How How -> Compiler Abstraction layer

    Optimization in the underlying layer Primitive as domain
  8. The logic of a computation without describing its control flow

  9. DOM manipulation is based on imperative operations

  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) ) } }) ); ;
  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 => => ` `<span> <span>${ ${s s} }</span> </span>` `) ). .join join( ('' '') ); ; } } Describing what to update the view
  12. = View(State)

  13. React updates views efficiently let let count count = =

    1 1; ; ReactDOM ReactDOM. .render render( ( < <div div> > < <Header Header / /> > < <p p> >{ {count count} }< </ /p p> > < </ /div div> >, , container container ) ); ; count count = = 2 2; ; ReactDOM ReactDOM. .render render( ( < <div div> > < <Header Header / /> > < <p p> >{ {count count} }< </ /p p> > < </ /div div> >, , container container ) ) // p.textContent = 2; // React updates the DOM // p.textContent = 2; // React updates the DOM
  14. ReactDOM Renderer

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

    problem domain
  16. Abstract your application components DOM is an implementation detail React

    Component is a primitive of your domain.
  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 Layout> > < <Header Header> >title title< </ /Header Header> > < <ItemList ItemList> > { {items items. .map map( (item item => => < <Item key Item key= ={ {item item. .id id} } item item= ={ {item item} } / /> >) )} } < </ /ItemList ItemList> > < <AddItemButton AddItemButton onAddItem onAddItem= ={ {item item => => setItems setItems( (items items. .concat concat( (item item) )) )} } / /> > < </ /Layout Layout> > ) ); ; } } ReactDOM ReactDOM. .render render( (< <App App / /> >, , view view) ); ;
  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
  19. React Custom Renderer

  20. Renderers

  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 Box> > < <Color green Color green> >Hello world Hello world! !< </ /Color Color> > < </ /Box Box> > ) ); ;
  22. ReactKonva ReactKonva ReactKonva. .render render( ( < <Stage width Stage

    width= ={ {300 300} } height height= ={ {300 300} }> > < <Layer Layer> > < <Text text Text text= ="Hello, world!" "Hello, world!" fontSize fontSize= ={ {30 30} } / /> > < <Star 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 ) ); ;
  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 mesh ref= ={ {ref ref} }> > < <boxBufferGeometry attach boxBufferGeometry attach= ="geometry" "geometry" args args= ={ {[ [1 1, , 1 1, , 1 1] ]} } / /> > < <meshNormalMaterial attach meshNormalMaterial attach= ="material" "material" / /> > < </ /mesh mesh> > ) ) } } ReactDOM ReactDOM. .render render( (< <Canvas Canvas> >< <Cube Cube / /> >< </ /Canvas Canvas> >, , el el) ); ;
  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 ClassDeclaration name= ="Hello" "Hello" superClassName superClassName= ="Array" "Array"> > < <Code Code> >const const hello hello = = 'world' 'world'< </ /Code Code> > < <FunctionDeclaration name FunctionDeclaration name= ="foo" "foo"> > < <Code Code> >return return 'bar' 'bar'< </ /Code Code> > < </ /FunctionDeclaration FunctionDeclaration> > < </ /ClassDeclaration ClassDeclaration> > ) ); ; console console. .log log( (ast ast) ); ;
  25. Building a Custom React DOM Renderer https://github.com/jquense/react-dom-lite https://conf.reactjs.org/event.html?sophiebits

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

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

  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) ); ; } } } }
  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
  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
  31. None
  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
  33. HostConfig? Side effects for a Host environment Define instances Define

    the mode for a renderer Hydration logic (if you need)
  34. Side effects for a Host environment ReactDOM ReactDOM. .render render(

    ( < <ul ul> > < <li key li key= ="a" "a"> >a a< </ /li li> > < <li key li key= ="b" "b"> >b b< </ /li li> > < <li key li key= ="c" "c"> >c c< </ /li li> > < </ /ul ul> >, , container container ) ); ; ReactDOM ReactDOM. .render render( ( < <ul ul> > < <li key li key= ="b" "b"> >b b< </ /li li> > < <li key li key= ="a" "a"> >a a< </ /li li> > < <li key 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);
  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) ); ; } }
  36. Others appendChild, appendInitialChild, appendChildToContainer commitTextUpdate, commitMount, commitUpdate insertBefore, insertInContainerBefore removeChild,

    removeChildFromContainer, resetTextContent
  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) ); ; } }
  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; } }
  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; ;
  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
  41. Live Coding

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