Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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. Summary React for the win • Elements, Instances and Components

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

    </p> /* React.createElement( "p", { style: { color: "green" } }, "Chandler?! Oh. My. God." ); */
  3. 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> ); } }
  4. Component function PokemonCard({ number = 0 }) { const pokemons

    = useContext(PokemonContext); const { name, imageSource } = pokemons[number]; return ( <> <p>{ name }</p> <img src={ imageSource } /> </> ); }
  5. 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 } /> ...
  6. 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.
  7. React provides a declarative API so that you don’t have

    to worry about exactly what changes on every update.
  8. 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.
  9. 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.
  10. 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
  11. 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.
  12. Component Elements of The Same Type <Abc>Noiceee</Abc> <Abc>Dude!</Abc> The instance

    stays the same, so that state is maintained across renders.
  13. 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.
  14. 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).
  15. A fiber represents a unit of work. It means that

    fiber represents a computation triggered by a data change.
  16. 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.
  17. React Renderers understand React component- driven architecture and it should

    provide such a declarative and composable abstraction as React. "Learn Once, Write Anywhere"
  18. 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
  19. 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);
  20. 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);
  21. 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> );
  22. Ok. Let’s build a custom renderer together? What about music?

    Thanks to Ken Wheeler I can't be that original as I thought :’(
  23. <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
 }
 }
 }
  24. 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; } };
  25. import Reconciler from 'react-reconciler'; const SongReconciler = Reconciler(hostConfig); We will

    create a reconciler instance using Reconciler which accepts a host config object.
  26. In this object we will define some methods which can

    be thought of as lifecycle of a renderer (update, append children, remove children, commit).
  27. createInstance, appendInitialChild, createTextInstance, finalizeInitialChildren, getPublicInstance, prepareForCommit, prepareUpdate, resetAfterCommit, resetTextContent, getRootHostContext,

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

    = props; const Note = MyAwesomeWebAPILibrary.createNote( ticks, number, duration, velocity ); return {type: "note", value: Note}; }
  32. 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"); }
  33. 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; }, ...
  34. ... appendInitialChild(parentInstance, child) { const { type } = child;

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

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