BT

Facilitating the Spread of Knowledge and Innovation in Professional Software Development

Write for InfoQ

Topics

Choose your language

InfoQ Homepage News Building a Custom React Renderer - Sophie Alpert at ReactConf 2019

Building a Custom React Renderer - Sophie Alpert at ReactConf 2019

Bookmarks

React developers may know that the renderer lives in a separate module from React. They may not necessarily know that they can write their own renderer by implementing the same interface than React DOM or React Native implements. Sophie Alpert, former manager of @reactjs team, gave in a talk at ReactConf 2019 a concrete example of how to implement a custom React renderer, leveraging the react-reconciler package.

A React component tree is made of host and non-host components. Host components are platform-specific components belonging to the host environment (like browsers, or mobile devices). In the case of a DOM host, these may be HTML elements such as div, or img. In the case of React Native, host components may include Text or View.

To create a custom renderer, developers need first to define a custom reconciler. The reconciler, among other things, computes a new component tree when necessary, compares that new tree to the current tree, computes a list of operations to turn the current tree into the new one, operations which are then executed (committed) in the host environment.

A reconciler instance is created from a Reconciler factory which accepts a host config object. The host config object defines a set of methods which will be called at various stages of the reconciling and rendering process (update, append children, remove children, commit) involving host components. React will manage all the non-host and user-defined components. The host config object admits a long series of methods. The most commonly used methods include:

const reconciler = Reconciler({
  finalizeInitialChildren(element, type, props) {  },
  getChildHostContext(parentContext, fiberType, rootInstance) {  },
  getRootHostContext(rootInstance) {  },
  shouldSetTextContent(type, props) {  },
  resetTextContent(element) {  },
  createTextInstance(text, rootContainerInstance, hostContext, internalInstanceHandle) {  },
  createInstance(type, props, rootContainerInstance, hostContext, internalInstanceHandle) {  },
  appendChildToContainer(container, child) { },
  appendChild(parent, child) { },
  appendInitialChild(parent, child) {  },
  removeChildFromContainer(container, child) { },
  removeChild(parent, child) { },
  insertInContainerBefore(container, child, before) { },
  insertBefore(parent, child, before) { },
  prepareUpdate(instance, type, oldProps, newProps, rootContainerInstance, currentHostContext) { },
  commitUpdate(instance, updatePayload, type, oldProps, newProps, finishedWork) { },
  prepareForCommit(...args) {  },
  resetAfterCommit(...args) {  },
  getPublicInstance(...args) {  },
  now: () => {}
  ... other methods

  supportsMutation: true
});

Alpert started with an empty host config and progressively implemented the necessary parts of the host config object interface in order to get the reconciler to render the following component tree on the screen:

<div className="App">
  <header className="App-header">
    <img src={logo} className="app-logo" alt="logo" />
    <p>
      Edit <code>src/App.js</code> and save to reload.
    </p>
    <a
      className="App-link"
      href="https://reactjs.org"
      target="_blank"
      ref="noopener noreferrer"
     >
       Learn React
    </a>
  </header>
</div>

Alpert at first implemented the createInstance, createTextInstance, appendChildToContainer, appendChild, appendInitialChild methods. On the first render of the previous component tree, the mentioned methods will be called by React in order to create host component instances (here DOM elements), and put them in the host environment (here the DOM). For instance, Alpert starts with a simple implementation of createInstance as follows:

function createInstance(type, props, rootContainerInstance, hostContext)[
  let el = document.createElement(type);
  if (props.className) el.className = props.className;
  if (props.src) el.src = props.src;
  return el
}

The set of methods implemented so far handle initial rendering, but are not enough to handle rendering an updated component tree. Alpert thus wrote a more involved component tree with hooks, effects, dynamic updates and continued implementing the renderer with the corresponding set of methods: removeChildFromContainer, removeChild, insertInContainerBefore, insertBefore. On updating a React component tree, the reconciler may decide to insert or remove nodes in the tree, which leads to calling some or all of the previous methods to realize the corresponding update in the host environment. As Alpert’s example targets the DOM, appendChild can for instance use the DOM API as follows:

function (parent, child){
  parent.appendChild(child);
}

Alpert then gave the implementations of prepareUpdate and commitUpdate, by adding a non-standard bgColor property to be rendered by Alpert’s custom renderer. For instance, prepareUpdate is as follows:

function prepareUpdate(instance, type, oldProps, newProps){
  let payload;
  if (oldProps.bgColor !== newProps.bgColor) {
    payload = {newBgColor: newProps.bgColor};
  }
  return payload;
}

commitUpdate in turn executes the update in the host environment:

function commitUpdate(instance, updatePayload, type, oldProps, newProps) {
  if (updatePayload.newBgColor) {
    instance.style.backgroundColor = updatePayload.newBgColor;
  }
},

With the custom reconciler implemented, Alpert finally wrote the custom renderer:

let ReactDOMMini = {
  render(whatToRender, div){
    let container = reconciler.createContainer(div, false, false);
    reconciler.updateContainer(whatToRender, container, null, null);
  }
}

There are a significant number of renderers already written for React, among which, react-three-fiber for the 3D library Three.js, react-nodegui for native desktop applications, ink for interactive command-line apps, React-pdf for PDF rendering, or react-sketchapp targeting Sketch.

Alpert’s full talk is available on ReactConf’s Youtube channel and contains further code snippets and explanations. A detailed list of custom react renderers is available in the awesome-react-renderer GitHub directory.

React Conf is the official Facebook React event. React Conf was held in 2019 in Henderson, Nevada, on October 24 & 25.

Rate this Article

Adoption
Style

BT