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

Writing Your Own React Renderer

Writing Your Own React Renderer

Talk about reconciler, life cycles, and custom-renderer. The idea is to introduce how to create a custom renderer and how the reconciler handles elements, components, and instances.

Raphael Amorim

November 30, 2018
Tweet

More Decks by Raphael Amorim

Other Decks in Programming

Transcript

  1. Writing

    Your
    Own
    React
    Renderer
    @raphamorims

    View Slide

  2. @raphamorims

    View Slide

  3. careers.godaddy.com

    View Slide

  4. Sorry folks.
    I don’t have discounts
    to domain purchase

    View Slide

  5. I’m
    from
    Rio.

    View Slide

  6. Summary
    React for the win
    • Elements, Instances and Components
    • Reconciler
    • Custom Renderers
    • React-Reconciler

    View Slide

  7. Element

    Chandler?! Oh. My. God.

    /*
    React.createElement(
    "p",
    { style: { color: "green" } },
    "Chandler?! Oh. My. God."
    );
    */

    View Slide

  8. Element

    // React.createElement(PokemonCard, { number: 26 });

    View Slide

  9. Element
    Plain object.

    Describe a component instance.

    It’s what you want to see on the screen.

    View Slide

  10. Component
    class PokemonTrainer extends React.Component {
    render() {
    const { name, imageSrc } = this.props;
    return (

    { name }


    );
    }
    }

    View Slide

  11. Component
    function PokemonCard({ number = 0 }) {
    const pokemons = useContext(PokemonContext);
    const { name, imageSource } = pokemons[number];
    return (
    <>
    { name }

    >
    );
    }

    View Slide

  12. Component
    Function or class with a render() method that
    inherits from React.Component.

    View Slide

  13. Instance
    class PokemonTrainerPicture extends React.Component {
    constructor(props) {
    super(props);
    this.imageRef = React.createRef();
    }
    render() {
    const { imageSrc } = this.props;
    return (
    className='pokemon-trainer--picture'
    ref={ this.imageRef }
    src={ imageSrc }
    />
    ...

    View Slide

  14. Instance
    Is what you refer to as this in the component
    class you write. It is useful for storing local
    state and reacting to the lifecycle events.

    View Slide

  15. View Slide

  16. Reconciliation.

    View Slide

  17. React provides a declarative API so
    that you don’t have to worry about
    exactly what changes on every
    update.

    View Slide

  18. React provides a declarative API so
    that you don’t have to worry about
    exactly what changes on every
    update.
    Reconciliation is responsible for doing that.

    View Slide

  19. Reconciliation is the React’s “diffing” algorithm.
    It makes component updates more predictable.
    When a component's state changes, React has to
    calculate if it is necessary to update the instances.

    View Slide

  20. https://reactjs.org/docs/reconciliation.html



    Elements of Different Types

    View Slide

  21. https://reactjs.org/docs/reconciliation.html






    Whenever the root elements have different types, React will tear
    down the old tree and build the new tree from scratch.
    Elements of Different Types

    View Slide

  22. https://reactjs.org/docs/reconciliation.html
    className="before"
    title="noice" 

    />
    Elements of The Same Type

    View Slide

  23. https://reactjs.org/docs/reconciliation.html
    className="before"
    title="noice" 

    />
    Elements of The Same Type
    className="after"
    title="noice" 

    />
    By comparing these two elements,
    Reconciler specifies what is going to
    be changed.
    In this case, React-DOM knows how
    to only modify the className on the
    underlying DOM node.

    View Slide

  24. Component Elements of The Same Type
    Noiceee Dude!
    The instance stays the same, so that state is maintained across renders.

    View Slide

  25. Fiber Reconciler.

    View Slide

  26. A fiber represents a unit of work.

    View Slide

  27. View Slide

  28. so..
    reconciliation: is the algorithm React uses to diff
    one tree with another to determine which parts
    need to be changed.
    update: A change in the data used to render a
    React app. Usually the result of setState.
    Eventually results in a re-render.

    View Slide

  29. scheduling: the process of determining when work
    should be performed.
    work: Any computations that must be performed.
    Work is usually the result of an update (e.g.
    setState).

    View Slide

  30. A fiber represents a unit of work.
    It means that fiber represents a computation
    triggered by a data change.

    View Slide

  31. This allow us to:
    - pause work and come back to it later.
    - assign priority to different types of work.
    - reuse previously completed work.
    - abort work if it's no longer needed.

    View Slide

  32. Custom Renderers

    View Slide

  33. React Renderers understand React component-
    driven architecture and it should provide such a
    declarative and composable abstraction as React.
    "Learn Once, Write Anywhere"

    View Slide

  34. github.com/chentsulin/awesome-react-renderer
    React DOM
    React Ape
    React TV
    React Titanium
    React Hardware
    React Native DOM
    React Native
    React Blessed
    React Gibbon
    React PDF Proton Native
    PDF Documents
    DOM Structure
    Netflix’s Internal renderer
    (WebGL)
    Canvas
    DOM Structure
    Terminal interface (using Blessed)
    Renderer for Appcelerator® Titanium™ SDK
    Build firmata-based hardware applications
    Port of React Native to the web
    Build native mobile apps*
    Build native desktop apps

    View Slide

  35. React Blessed

    View Slide

  36. React Blessed
    import React, {Component} from 'react';
    import blessed from 'blessed';
    import {render} from 'react-blessed';
    // Rendering a simple centered box
    class App extends Component {
    render() {
    return (
    left="center"
    width="50%"
    height="50%"
    border={{type: 'line'}}
    style={{border: {fg: 'blue'}}}>
    Hello World!

    );
    }
    }
    // Creating our screen
    const screen = blessed.screen({
    autoPadding: true,
    smartCSR: true,
    title: 'react-blessed hello world'
    });
    // Adding a way to quit the program
    screen.key(['escape', 'q', ‘C-c'], (ch, key) => {
    return process.exit(0);
    });
    // Rendering the React app using our screen
    const component = render(, screen);

    View Slide

  37. React Hardware
    import React from 'react';
    import ReactHardware, {Led} from 'react-hardware';
    const HIGH = 255;
    const LOW = 0;
    class Application extends React.Component {
    constructor(props) {
    super(props);
    this.state = {value: 0};
    this._timer = null;
    }
    componentDidMount() {
    this._timer = setInterval(_ => (
    this.setState(prevState => ({value: prevState.value === HIGH ? LOW : HIGH}))
    ), this.props.interval);
    }
    componentDidUnmount() {
    clearInterval(this._timer);
    this._timer = null;
    }
    render() {
    return (

    );
    }
    }
    var PORT = '/dev/tty.usbmodem1411';
    ReactHardware.render(, PORT);

    View Slide

  38. React PDF
    import React from 'react';
    import { Document, Page, Text, View, StyleSheet } from '@react-pdf/renderer';
    // Create styles
    const styles = StyleSheet.create({
    page: {
    flexDirection: 'row',
    backgroundColor: '#E4E4E4'
    },
    section: {
    margin: 10,
    padding: 10,
    flexGrow: 1
    }
    });
    // Create Document Component
    const MyDocument = () => (



    Section #1


    Section #2



    );

    View Slide

  39. View Slide

  40. How to Start?

    View Slide

  41. https://www.npmjs.com/package/react-reconciler

    View Slide

  42. View Slide

  43. Ok.
    Let’s build a custom renderer together?
    What about music?
    Thanks to Ken Wheeler I can't be that original as I thought :’(

    View Slide

  44. What we want to build?

    View Slide

  45. View Slide


  46. {

    type: "Song",

    props: {

    bpm: 100,

    }

    }

    View Slide


  47. duration={25} 

    ticks={15}

    number={100}

    />


    {

    type: "Song",

    props: {

    bpm: 100,

    children: {

    type: "Note",

    duration: 25,

    ticks: 15,

    number: 100

    }

    }

    }

    View Slide

  48. WebAPI?

    View Slide

  49. const MyAwesomeWebAPILibrary = {
    createSong: config => {
    // ...
    return Song;
    },
    createNote: (ticks, number, velocity, duration) => {
    // ...
    return Note;
    },
    createMIDI: data => {
    // data: or base64
    const MIDI = new Promise((resolve, reject) => {
    // ...
    resolve(MidiFile);
    })
    return MIDI;
    }
    };

    View Slide

  50. // set the custom types
    export const Song = 'Song';
    export const Note = 'Note';

    View Slide

  51. import Reconciler from 'react-reconciler';
    const SongReconciler = Reconciler(hostConfig);
    We will create a reconciler instance using
    Reconciler which accepts a host config object.

    View Slide

  52. In this object we will define some methods which can be
    thought of as lifecycle of a renderer (update, append
    children, remove children, commit).

    View Slide

  53. createInstance, appendInitialChild, createTextInstance,
    finalizeInitialChildren, getPublicInstance,
    prepareForCommit, prepareUpdate, resetAfterCommit,
    resetTextContent, getRootHostContext,
    getChildHostContext, scheduleAnimationCallback,
    scheduleDeferredCallback, useSyncScheduling, now,
    shouldSetTextContent, mutation […]

    View Slide

  54. createInstance, appendInitialChild, createTextInstance,
    finalizeInitialChildren, getPublicInstance,
    prepareForCommit, prepareUpdate, resetAfterCommit,
    resetTextContent, getRootHostContext,
    getChildHostContext, scheduleAnimationCallback,
    scheduleDeferredCallback, useSyncScheduling, now,
    shouldSetTextContent, mutation […]
    Also, I challenge
    Ken Wheeler to remix
    this into a song.

    View Slide

  55. const SongReconciler = Reconciler({
    createInstance(type, props) {},
    // e.g. DOM renderer returns a DOM node
    supportsMutation: true, // it works by mutating nodes
    mutation: {}, // mutation operations
    appendChild(parent, child) {
    // e.g. DOM renderer would call .appendChild() here
    },

    });

    View Slide

  56. class Song {
    constructor({ metronome, bpm, bars}) {
    this.notes = [];
    this.config = {
    metronome, bpm, bars,
    }
    }
    addNote(note) {
    this.notes = this.notes.concat(note);
    }
    render() {
    let { notes, config } = this;
    const { metronome, bpm, bars } = config;
    const song = MyAwesomeWebAPILibrary.createSong({
    bars, bpm, notes, metronome
    });
    song.play();
    }
    }

    View Slide

  57. function createNote(props) {
    const {
    ticks, number, duration, velocity
    } = props;
    const Note = MyAwesomeWebAPILibrary.createNote(
    ticks, number, duration, velocity
    );
    return {type: "note", value: Note};
    }

    View Slide

  58. function createCustomInstance(type, props) {
    const COMPONENTS = {
    Song: () => new Song(props),
    Note: () => createNote(props)
    };
    return COMPONENTS[type]
    ? COMPONENTS[type]()
    : console.warn("ins't a valid element type");
    }

    View Slide

  59. const SongReconciler = reconciler({
    createInstance(
    type, // Song
    props, // {}
    rootContainerInstance, // or any root target
    hostContext, // NoContext
    internalInstanceHandle
    ) {
    const customInstance = createCustomInstance(
    type,
    props
    );
    return customInstance;
    },
    ...

    View Slide

  60. ...
    appendInitialChild(parentInstance, child) {
    const { type } = child;
    if (type === 'note') {
    parentInstance.addNote(child.value);
    }
    },
    ...
    appendInitialChild

    View Slide

  61. ...
    finalizeInitialChildren(element, type, props) {
    if (element && element.render) {
    element.render(); // Song.render()
    }
    },
    ...
    finalizeInitialChildren

    View Slide

  62. Song
    createInstance

    View Slide

  63. Song
    createNote
    appendInitialChild
    createNote createNote
    createNote createNote createNote

    View Slide

  64. Song
    Notes Notes Notes
    Notes Notes Notes
    finalizeInitialChildren

    View Slide

  65. And this concept is universal.
    You can build anything using components abstraction.

    View Slide

  66. Give a try!

    View Slide

  67. github.com/raphamorim/react-ape

    View Slide

  68. Arts:
    http://avazaki.tumblr.com/post/168544406673
    http://worldoro.tumblr.com/image/171366085872
    https://reactjs.org/blog/2015/12/18/react-components-elements-and-instances.html

    https://reactjs.org/blog/2014/10/14/introducing-react-elements.html

    https://reactjs.org/docs/glossary.html

    https://github.com/abudaan/heartbeat

    https://medium.com/@agent_hunt/hello-world-custom-react-renderer-9a95b7cd04bc

    https://www.npmjs.com/package/react-reconciler

    https://github.com/nitin42/Making-a-custom-React-renderer

    https://github.com/acdlite/react-fiber-architecture

    https://reactjs.org/docs/reconciliation.html
    References

    View Slide

  69. Obrigado!
    @raphamorims
    Thanks!

    View Slide