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.

Ed35943d3199ea37b1b60c39615e8163?s=128

Raphael Amorim

November 30, 2018
Tweet

Transcript

  1. Writing
 Your Own React Renderer @raphamorims

  2. @raphamorims

  3. careers.godaddy.com

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

  5. I’m from Rio.

  6. Summary React for the win • Elements, Instances and Components

    • Reconciler • Custom Renderers • React-Reconciler
  7. Element <p style={{ color: "green" }}> Chandler?! Oh. My. God.

    </p> /* React.createElement( "p", { style: { color: "green" } }, "Chandler?! Oh. My. God." ); */
  8. Element <PokemonCard number={26}/> // React.createElement(PokemonCard, { number: 26 });

  9. Element Plain object. Describe a component instance.
 It’s what you

    want to see on the screen.
  10. Component class PokemonTrainer extends React.Component { render() { const {

    name, imageSrc } = this.props; return ( <div className='trainer'> <p className='trainer--name'>{ name }</p> <img className='trainer--picture' src={ imageSrc }/> </div> ); } }
  11. Component function PokemonCard({ number = 0 }) { const pokemons

    = useContext(PokemonContext); const { name, imageSource } = pokemons[number]; return ( <> <p>{ name }</p> <img src={ imageSource } /> </> ); }
  12. Component Function or class with a render() method that inherits

    from React.Component.
  13. Instance class PokemonTrainerPicture extends React.Component { constructor(props) { super(props); this.imageRef

    = React.createRef(); } render() { const { imageSrc } = this.props; return ( <img className='pokemon-trainer--picture' ref={ this.imageRef } src={ imageSrc } /> ...
  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.
  15. None
  16. Reconciliation.

  17. React provides a declarative API so that you don’t have

    to worry about exactly what changes on every update.
  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.
  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.
  20. https://reactjs.org/docs/reconciliation.html <div> <PokemonCard /> </div> Elements of Different Types

  21. https://reactjs.org/docs/reconciliation.html <div> <PokemonCard /> </div> <span> <PokemonCard /> </span> 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
  22. https://reactjs.org/docs/reconciliation.html <div className="before" title="noice" 
 /> Elements of The Same

    Type
  23. https://reactjs.org/docs/reconciliation.html <div className="before" title="noice" 
 /> Elements of The Same

    Type <div 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.
  24. Component Elements of The Same Type <Abc>Noiceee</Abc> <Abc>Dude!</Abc> The instance

    stays the same, so that state is maintained across renders.
  25. Fiber Reconciler.

  26. A fiber represents a unit of work.

  27. None
  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.
  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).
  30. A fiber represents a unit of work. It means that

    fiber represents a computation triggered by a data change.
  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.
  32. Custom Renderers

  33. React Renderers understand React component- driven architecture and it should

    provide such a declarative and composable abstraction as React. "Learn Once, Write Anywhere"
  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
  35. React Blessed

  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 ( <box top="center" left="center" width="50%" height="50%" border={{type: 'line'}} style={{border: {fg: 'blue'}}}> Hello World! </box> ); } } // 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(<App />, screen);
  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 ( <Led pin={10} value={this.state.value} /> ); } } var PORT = '/dev/tty.usbmodem1411'; ReactHardware.render(<Application interval={1000} />, PORT);
  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 = () => ( <Document> <Page size="A4" style={styles.page}> <View style={styles.section}> <Text>Section #1</Text> </View> <View style={styles.section}> <Text>Section #2</Text> </View> </Page> </Document> );
  39. None
  40. How to Start?

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

  42. None
  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 :’(
  44. What we want to build?

  45. None
  46. <Song bpm={100}/> {
 type: "Song",
 props: {
 bpm: 100,
 }


    }
  47. <Song bpm={100}>
 <Note duration={25} 
 ticks={15}
 number={100}
 />
 </Song> {


    type: "Song",
 props: {
 bpm: 100,
 children: {
 type: "Note", duration: 25, ticks: 15, number: 100
 }
 }
 }
  48. WebAPI?

  49. const MyAwesomeWebAPILibrary = { createSong: config => { // ...

    return Song; }, createNote: (ticks, number, velocity, duration) => { // ... return Note; }, createMIDI: data => { // data: <ArrayBuffer> or base64 const MIDI = new Promise((resolve, reject) => { // ... resolve(MidiFile); }) return MIDI; } };
  50. // set the custom types export const Song = 'Song';

    export const Note = 'Note';
  51. import Reconciler from 'react-reconciler'; const SongReconciler = Reconciler(hostConfig); We will

    create a reconciler instance using Reconciler which accepts a host config object.
  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).
  53. createInstance, appendInitialChild, createTextInstance, finalizeInitialChildren, getPublicInstance, prepareForCommit, prepareUpdate, resetAfterCommit, resetTextContent, getRootHostContext,

    getChildHostContext, scheduleAnimationCallback, scheduleDeferredCallback, useSyncScheduling, now, shouldSetTextContent, mutation […]
  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.
  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 }, … });
  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(); } }
  57. function createNote(props) { const { ticks, number, duration, velocity }

    = props; const Note = MyAwesomeWebAPILibrary.createNote( ticks, number, duration, velocity ); return {type: "note", value: Note}; }
  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"); }
  59. const SongReconciler = reconciler({ createInstance( type, // Song props, //

    {} rootContainerInstance, // <div id=“root”/> or any root target hostContext, // NoContext internalInstanceHandle ) { const customInstance = createCustomInstance( type, props ); return customInstance; }, ...
  60. ... appendInitialChild(parentInstance, child) { const { type } = child;

    if (type === 'note') { parentInstance.addNote(child.value); } }, ... appendInitialChild
  61. ... finalizeInitialChildren(element, type, props) { if (element && element.render) {

    element.render(); // Song.render() } }, ... finalizeInitialChildren
  62. Song createInstance

  63. Song createNote appendInitialChild createNote createNote createNote createNote createNote

  64. Song Notes Notes Notes Notes Notes Notes finalizeInitialChildren

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

    components abstraction.
  66. Give a try!

  67. github.com/raphamorim/react-ape

  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
  69. Obrigado! @raphamorims Thanks!