Last month Ant Financial announced a new framework, Remax, for developing applets using the real, complete React.

For React developers, ‘Learn once, write anywhere’ is a similar development experience to ReactNative. For small applications, it’s a completely new development experience.

Taro claims to be a “React like” development solution, but it is implemented using static compilation, as mentioned by Byryu in his article “Remax – Building Small Programs with the Real React” :

The so-called static compilation, is the use of tools to analyze the code syntax, the JSX part and the logical part of the extraction, respectively generate small program template and Page definition.

This solution is complicated to implement, and React does not exist at runtime.


By comparison, Remax’s solution is much simpler, and it’s nothing more than the new React renderer.


Since Remax has just been released, the core code is relatively simple. If you are interested, you can go to Github to see the contributions and try out the custom Renderer at CodeSandbox: Edit React-custom -renderer article seems to be a long one. Step by step: 🦖


The article Outlines

  • Some basic concepts about React
  • Customize the React renderer
  • HostConfig renderer ADAPTS
  • The host components
  • Mirror tree construction and operation
  • Node updates
  • Side effect submission
  • HostConfig execution process summary
  • Sync to the renderer
  • conclusion
  • Further reading


Some basic concepts about React

To create a React custom renderer, you need to understand the basic principles of React rendering. So before diving into this article, make sure you understand the following basic concepts:

1. Element

We can create an Element using JSX or react. createElement to describe the view node we want to create. Such as:

<button class='button button-blue'>
  <b>
    OK!
  </b>
</button>
Copy the code

JSX will be escaped as:

React.createElement(
  "button",
  { class: 'button button-blue' },
  React.createElement("b".null."OK!"))Copy the code

React.createElement eventually builds an object that looks like this:

{
  type: 'button'.props: {
    className: 'button button-blue'.children: {
      type: 'b'.props: {
        children: 'OK! '}}}}Copy the code

That is, an Element is a generic object that describes the node types, props, and children created by the user. These Elements are combined to form a tree describing the user view


2. Component

Can be thought of as the type of Element, which comes in two types:

  • Host Component: a ‘built-in’ Component provided by the rendering platform, such as a DOM node under the ReactDOM platform, such as div, SPAN… These component types are strings

  • Composite Component: Composite Component, which is a user-defined unit of Component encapsulation. Usually contains custom logic, state, and an output Element tree. Compound types can be classes or functions

const DeleteAccount = (a)= > (
  <div>
    <p>Are you sure?</p>
    <DangerButton>Yep</DangerButton>
    <Button color='blue'>Cancel</Button>
  </div>
);
Copy the code


3. Instance

When React starts rendering an Element, it creates an ‘instance’ of it based on the component type, such as a class component, which is instantiated by calling the new operator. This instance will be referenced until Element is removed from the Element Tree.

First render: React instantiates a MyButton instance, calls the mount associated lifecycle method, and executes the Render method to recursively render the child

render(<MyButton>foo</MyButton>, container)
Copy the code


Update: Because the component type does not change, React is no longer instantiated. This is a ‘node update’ and React executes update-related lifecycle methods, such as shouldComponentUpdate. If an update is required, the Render method is executed again

render(<MyButton>bar</MyButton>, container)
Copy the code


Uninstall: The component type is different and the old MyButton has been replaced. The instance of MyButton will be destroyed, and React will perform unmount related lifecycle methods, such as componentWillUnmount

render(<button>bar</button>, container)
Copy the code


4. Reconciler & Renderer

The relationship between the Reconciler and the Renderer can be clearly traced in the following figure.

The Reconciler is responsible for maintaining the VirtualDOM tree, and the Diff/Fiber algorithm is implemented internally to decide when and what to update

The Renderer is responsible for platform-specific rendering, providing hosting components, handling events, and so on. ReactDOM, for example, is a renderer responsible for DOM node rendering and DOM event handling.



With The React architecture, the update process is divided into two phases.

  • React identifies nodes that need to be updated. This phase can be interrupted, for example when there are higher-priority events to deal with.
  • The Commit Phase executes all the ** Effects ** calculated in the previous Phase that need to be addressed at once. This phase must be executed synchronously and without interruption


If bounded by render, life cycle functions can be divided into two stages:

  • Coordinate phase
    • constructor
    • componentWillMountabandoned
    • componentWillReceivePropsabandoned
    • static getDerivedStateFromProps
    • shouldComponentUpdate
    • componentWillUpdateabandoned
    • render
    • getSnapshotBeforeUpdate()
  • The commit phase
    • componentDidMount
    • componentDidUpdate
    • componentWillUnmount


Don’t understand? This may be a bit hard for you to read, but it is recommended to read some articles on the basic principles of React.


At present, most of the core work of React has been completed in Reconciler. Fortunately, the architecture and module division of React are still fairly clear, and React has also exposed some libraries, which greatly simplifies the difficulty of developing Renderer. Let’s go!


Customize the React renderer

React officially exposes some libraries for developers to extend custom renderers:

  • The React-Reconciler – this is the react coordinator, the heart of React. We mainly use it to develop renderers.
  • Some APIS for scheduler – Cooperative scheduler. This article will not use

It should be noted that these packages are experimental and the apis may be unstable. In addition, there is no detailed documentation, you need to look at the source code or other renderer implementation; This article and the articles in the extended reading are also good learning materials.


Creating a custom renderer takes just two steps:

The first step: implement the host configuration, which is a number of adapter methods and configuration items that the React-Reconciler requires from the host. These configuration items define how to create a node instance, build a node tree, commit, update, and so on. These configuration items are described in detail below

const Reconciler = require('react-reconciler');

const HostConfig = {
  / /... Implement adapter methods and configuration items
};
Copy the code


Step 2: Implement the render function, similar to the reactdom.render () method

// Create a Reconciler instance and pass HostConfig to the Reconciler
const MyRenderer = Reconciler(HostConfig);

/** * Rendered (
      , container, () => console.log(' Rendered ')) */
export function render(element, container, callback) {
  // Create root container
  if(! container._rootContainer) { container._rootContainer = ReactReconcilerInst.createContainer(container,false);
  }

  // Update the root container
  return ReactReconcilerInst.updateContainer(element, container._rootContainer, null, callback);
}
Copy the code

The Container is both the mounting target of the React component tree (e.g., ReactDOM is normally mounted to the #root element, which is a container) and the Fiber root node of the component tree. The root node is the entry point to the entire component tree, which will be used to hold some information and manage updates and rendering of all the nodes.

For more details on the Fiber architecture, see these articles:

  • React Fiber Architecture and Source Code
  • React Fiber for those of you who have the ability to read Lin Clark’s talk


HostConfig renderer ADAPTS

HostConfig supports a wide range of parameters, the full list can be found here. Here are some of the parameters that custom renderers must provide:

interface HostConfig {
  /** * Used to share some context information */
  // Get the context information of the root container, called only once at the root node
  getRootHostContext(rootContainerInstance: Container): HostContext;
  // Get the context information of the child nodes, called each time a node is traversed
  getChildHostContext(parentHostContext: HostContext, type: Type, rootContainerInstance: Container): HostContext;


  /** * Node instance creation */
  // Create a normal node instance, such as the Element type of the DOM
  createInstance(type: Type, props: Props, rootContainerInstance: Container, hostContext: HostContext, internalInstanceHandle: OpaqueHandle,): Instance;
  // Create a Text node, such as the DOM Text type
  createTextInstance(text: string, rootContainerInstance: Container, hostContext: HostContext, internalInstanceHandle: OpaqueHandle): TextInstance;
  // Decide whether to process child nodes/child text nodes. Return true if you do not want to create one. For example, if dangerouslySetInnerHTML is used in ReactDOM, the child node is ignored
  shouldSetTextContent(type: Type, props: Props): boolean;

  /** * Node tree construction */
  // This is called to add child nodes if the node is in the * unmounted * state
  appendInitialChild(parentInstance: Instance, child: Instance | TextInstance): void;
  // ** The following are all effects, which are executed in the commit phase **
  // Add child nodesappendChild? (parentInstance: Instance,child: Instance | TextInstance): void;
  // Add child node to container node (root node)appendChildToContainer? (container: Container,child: Instance | TextInstance): void;
  // Insert child nodesinsertBefore? (parentInstance: Instance,child: Instance | TextInstance, beforeChild: Instance | TextInstance): void;
  // Insert child node into container node (root node)insertInContainerBefore? (container: Container,child: Instance | TextInstance, beforeChild: Instance | TextInstance,): void;
  // Delete the child noderemoveChild? (parentInstance: Instance,child: Instance | TextInstance): void;
  // Remove child nodes from the container node (root node)removeChildFromContainer? (container: Container,child: Instance | TextInstance): void;

  /** * The node mounts */
  // Called when all child nodes are initialized (all appendInitialChild ends), and if true is returned, commitMount will be fired
  // ReactDOM implements the autofocus functionality of form elements through this attribute and commitMount configuration
  finalizeInitialChildren(parentInstance: Instance, type: Type, props: Props, rootContainerInstance: Container, hostContext: HostContext): boolean;
  // In conjunction with finalizeInitialChildren, commitRoot executes after resetAfterCommit, that is, after the component tree is renderedcommitMount? (instance: Instance,type: Type, newProps: Props, internalInstanceHandle: OpaqueHandle): void;

  /** * Node update */
  // Prepare a node update. If nothing is returned, there is no update, and commitUpdate is not called
  prepareUpdate(instance: Instance, type: Type, oldProps: Props, newProps: Props, rootContainerInstance: Container, hostContext: HostContext,): null | UpdatePayload;
  // ** The following are all effects, which are executed in the commit phase **
  // Text node submissioncommitTextUpdate? (textInstance: TextInstance,oldText: string, newText: string): void;
  // Common node commitcommitUpdate? (instance: Instance,updatePayload: UpdatePayload, type: Type, oldProps: Props, newProps: Props, internalInstanceHandle: OpaqueHandle): void;
  // Reset the normal node text content. This needs to be used with shouldSetTextContent(if true),resetTextContent? (instance: Instance):void;

  /** * submit */
  // it is called before the commit is started. For example, here you can save some state and restore the state after the commit is complete. For example, ReactDOM stores the focus state of the current element, which is restored after the commit
  // After prepareForCommit, Effects will be executed.
  prepareForCommit(containerInfo: Container): void;
  PrepareForCommit, which is executed after the commit is complete
  resetAfterCommit(containerInfo: Container): void;


  /** ** */
  This function is used by Reconciler to calculate the current time, such as the time remaining in a task
  // Performance. Now is preferred in ReactDOM. Date.now is preferred in common scenarios
  now(): number;
  // Custom timer
  setTimeout(handler: (. args: any[]) = > void.timeout: number): TimeoutHandle | NoTimeout;
  // Cancel the timer
  clearTimeout(handle: TimeoutHandle | NoTimeout): void;
  // represents an empty timer, as shown in the signature of 👆clearTimeout
  noTimeout: NoTimeout;

  / /? Function of the unknown
  shouldDeprioritizeSubtree(type: Type, props: Props): boolean;
  / / abandoned
  scheduleDeferredCallback(callback: (a)= >any, options? : {timeout: number }): any;
  / / abandoned
  cancelDeferredCallback(callbackID: any): void;


  /** * Function enabled */
  // Enable node modification, usually the renderer is enabled, otherwise the node cannot be updated
  supportsMutation: boolean;
  // Enable persistence?
  supportsPersistence: boolean;
  // Enable Hydrate, usually used for server rendering
  supportsHydration: boolean;

  /** * miscellaneous */
  // Get the publicly available node instance, that is, the node information you want to expose to the user. The user can get this object through ref. Custom renderers generally return as is, unless you want to selectively expose information to the user
  getPublicInstance(instance: Instance | TextInstance): PublicInstance;

  / /... There are a lot of parameters, but we won't go into them because we don't normally use them in renderers
}
Copy the code


If you divide the interface into two phases of Fiber, it looks like this:

Coordinate phase To submit The commit phase Submit completed
createInstance prepareCommit appendChild resetAfterCommit
createTextInstance appendChildToContainer commitMount
shouldSetTextContent insertBefore
appendInitialChild insertInContainerBefore
finalizeInitialChildren removeChild
prepareUpdate removeChildFromContainer
commitTextUpdate
commitUpdate
resetTextContent


From the above interface definition, we can know that HostConfig configuration is rich, involving node operation, mount, update, scheduling, and various life cycle hooks, which can control various behaviors of the renderer.

A little loopy? That’s ok, you don’t need to know all the parameters yet, but we’ll explain them bit by bit below. You can come back to this one last time.


The host components

React has two Component types: Host Component and CompositeComponent. Host components are provided by the platform. For example, the ReactDOM platform provides div, SPAN, H1… These components are usually strings that are rendered directly as view nodes under the platform.

Composite components, also known as custom components, are used to combine other composite components with host components, usually classes or functions.

The renderer does not need to care about the processing of the composite components; what is handed to the renderer is a tree of host components.

Of course, there are many applets specific host components defined in Remax, for example, we can use them like this:

function MyComp() {
  return <view><text>hello world</text></view>
}
Copy the code


The Reconciler calls createInstance and createTextInstance of HostConfig to create instances of the host component, so the custom renderer must implement both methods. See how Remax does it:

const HostConfig = {
  // Create a host component instance
  createInstance(type: string, newProps: any, container: Container) {
    const id = generate();
    // Preprocessing props, remax does some special processing for the event type props
    const props = processProps(newProps, container, id);
    return new VNode({
      id,
      type,
      props,
      container,
    });
  },

  // Create a host component text node instance
  createTextInstance(text: string, container: Container) {
    const id = generate();
    const node = new VNode({
      id,
      type: TYPE_TEXT,
      props: null,
      container,
    });
    node.text = text;
    return node;
  },

  // Determine whether child nodes need to be processed. If true is returned, it is not created and the entire sub-tree of components is ignored.
  // Some scenarios do not require the creation of text nodes, but are digested internally by the parent node.
  For example, in ReactDOM, if dangerouslySetInnerHTML is set for a node, its children should be ignored,
  ShouldSetTextContent should return true
  shouldSetTextContent(type, nextProps) {
    return false}}Copy the code

In the ReactDOM, the above two methods create the host component (the DOM node) through Document. createElement and Document. createTextNode, respectively.


Above is the micro channel small program architecture diagram (image source: together take off the coat of small program – micro channel small program architecture analysis).

Because the applet isolates the renderer process from the logic process. Remax is run on the logical process, in the logical process can not carry out the actual rendering, can only pass the update instruction to the rendering process through setData, and then parse rendering.

So Remax chose to create a Mirror Tree in the logical process and then synchronize it to the rendering process, as shown below:


The above VNode is the virtual node in the image tree. It is mainly used to store some node information without any special processing. Its structure is as follows:

export default class VNode {
  id: number;                  // A unique node ID
  container: Container;
  children: VNode[];           / / child nodes
  mounted = false;             // Whether the node has been mounted
  type: string | symbol;       // Node typeprops? : any;// Props of the node
  parent: VNode | null = null; // Parent node referencetext? : string;// If it is a text node, the text content is saved here
  path(): Path                 // The path of the node. After synchronization to the renderer process, restore to the tree via path
  // Child node operation
  appendChild(node: VNode, immediately: boolean)
  removeChild(node: VNode, immediately: boolean)
  insertBefore(newNode: VNode, referenceNode: VNode, immediately: boolean)

  update()                     // Triggers synchronization to the renderer
  toJSON(): string
}
Copy the code

The full VNode code can be seen here


Mirror tree construction and operation

To build a complete node tree you need to implement HostConfig’s appendChild, insertBefore, removeChild, etc. These methods are easy to understand and therefore don’t need much explanation.

const HostConfig = {
  // ...

  // Node modification is supported
  // Some statically rendered scenes, such as PDF files, can be closed
  // When closed, just implement appendInitiaChild
  supportsMutation: true.// Used to add child nodes during initialization (first time)
  appendInitialChild: (parent: VNode, child: VNode) = > {
    parent.appendChild(child, false);
  },

  // Add child nodes
  appendChild(parent: VNode, child: VNode) {
    parent.appendChild(child, false);
  },

  // Insert child nodes
  insertBefore(parent: VNode, child: VNode, beforeChild: VNode) {
    parent.insertBefore(child, beforeChild, false);
  },

  // Delete a node
  removeChild(parent: VNode, child: VNode) {
    parent.removeChild(child, false);
  },

  // Add nodes to container nodes. In general we do not need to make a special distinction with appendChild
  appendChildToContainer(container: any, child: VNode) {
    container.appendChild(child);
    child.mounted = true;
  },

  // Insert node to container node
  insertInContainerBefore(container: any, child: VNode, beforeChild: VNode) {
    container.insertBefore(child, beforeChild);
  },

  // Remove the node from the container node
  removeChildFromContainer(container: any, child: VNode) { container.removeChild(child); }},Copy the code


Node updates

In the previous section, we looked at updates at the tree structure level. Updates are also required when node attributes change or text content changes. We can handle such updates with the following HostConfig configuration:

const HostConfig = {
  /** * Update related */
  // This is the same as shouldComponentUpdate for the React component if the props are not changed
  // If ** returns' null ', the node is not updated, and commitUpdate is not called
  prepareUpdate(node: VNode, type: string, oldProps: any, newProps: any) {
    oldProps = processProps(oldProps, node.container, node.id);
    newProps = processProps(newProps, node.container, node.id);
    if(! shallowequal(newProps, oldProps)) {return true;
    }
    return null;
  },

  // Update the node
  commitUpdate(
    node: VNode,
    updatePayload: any,
    type: string,
    oldProps: any,
    newProps: any
  ) {
    node.props = processProps(newProps, node.container, node.id);
    node.update();
  },

  // Update the text node
  commitTextUpdate(node: VNode, oldText: string, newText: string) {
    if(oldText ! == newText) { node.text = newText;// Update the nodenode.update(); }}},Copy the code

Ok, this is also easier to understand. For ordinary node updates, prepareUpdate is first called to determine whether to update, and if non-empty data is returned, the Reconciler puts the node into the Effects chain, and commitUpdate is called during the commit phase to perform the update. Text node updates call commitTextUpdate without further delay.


Side effect submission

The concept of two phases of the React update is very important, and this is also reflected in HostConfig:

const HostConfig = {
  It is time for me to submit, "Reconciler said." What do you do before you submit it, right here
  For example, ReactDOM stores the selected and focused states of the current DOM document and disables event handling. DOM updates can break these states
  prepareForCommit: (a)= > {},

  "I have submitted it," Reconciler said
  // ReactDOM restores the selected and focused state of the DOM document before the submission
  resetAfterCommit: (a)= > {},




  // Called in the coordination phase when a node is' created '. If there are child nodes, it is called after all child nodes appendInitialChild has completed
  // Returns a Boolean value indicating whether to call commitMount after the commit is complete. In layman's terms, this is to tell the Reconciler that there is something to be done after the current node is "mounted.
  // ReactDOM uses this hook to process nodes with autofoucs attributes, automatically retrieving focus on commitMount
  finalizeInitialChildren: (a)= > false.The Reconciler is used in conjunction with finalizeInitialChildren. If the former Reconciler returns true, the commitMount of the corresponding nodes is executed after the Reconciler is completed
  commitMount: (a)= >{},}Copy the code


Aggregate all the hooks mentioned above and divide them by update phase and application target. Their distribution looks like this:


So when should Remax commit an ‘update’ to the rendering process? The answer is all methods called in the commit phase.

The commit phase is intended to perform a variety of side effects, such as view updates, remote method requests, subscriptions… So Remax also collects update instructions at this stage and pushes them to the renderer in the next loop.


HostConfig execution process summary

To review the flow of custom renderer method calls, first take a look at the mounted flow:

Suppose our component structure looks like this:

const container = new Container()
const MyComp = (a)= > {
  return (
    <div>
      <span>hello world</span>
    </div>
  )
}

render(
  <div className="root">
    <MyComp />
    <span>--custom renderer</span>
  </div>,
  container,
  () => {
    console.log("rendered")},Copy the code

The React component tree looks like this (left), but for the renderer, the tree looks like this (right). Custom components are at the React level, and the renderer only needs to care about the view structure that ultimately needs to be rendered. In other words, the renderer only cares about the host component:


Mounting will go through the following process:

From the flowchart above, you can clearly see when each hook is called.


Similarly, let’s look at the process of node update. Let’s modify the above program slightly so that it triggers periodic updates:

const MyComp = (a)= > {
  const [count, setCount] = useState(1)
  const isEven = count % 2= = =0
  useEffect((a)= > {
    const timer = setInterval((a)= > {
      // Increment the counter
      setCount(c= > c + 1)},10000)

    return (a)= > clearInterval(timer)
  }, [])

  return (
    <div className="mycomp" style={{ color: isEven ? "red" : "blue}} ">
      {isEven ? <div>even</div> : null}
      <span className="foo">hello world {count}</span>
    </div>)}Copy the code


Here is the update process:


When MyComp’s count changes from 1 to 2, MyComp is rerendered with a new div node (red dotted box) and Hello World 1 becomes Hello World 2.

The new DIV node creation process is the same as when it was mounted, except that instead of being inserted immediately into the parent node, it is placed in the Effect linked list first and executed at commit time.

Similarly, updates to the Hello World {count} text node and Props updates to other nodes are placed in the effects list and submitted at the last minute. As above, insertBefore, commitTextUpdate, commitUpdate.

The other key is the prepareUpdate hook, where you can tell the Reconciler whether a node needs to be updated, and if it does, return a non-null value so that the commitUpdate is fired.


Sync to the renderer

The React custom renderer is more or less there, and platform-specific things come next. Remax currently does this by passing the update instruction to the renderer process via the setData method of the Page object in the applet. The rendering process restores the update instruction to the tree through WXS mechanism. Finally, the tree is rendered recursively through the template mechanism.

The overall structure is as follows:


Let’s take a look at how the logical process pushes the update command:

// Manage updates on the root container
export default class Container {
  // ...
  // Trigger the update
  requestUpdate(
    path: Path,
    start: number,
    deleteCount: number,
    immediately: boolean, ... items: RawNode[] ) {const update: SpliceUpdate = {
      path, // Update the tree path of the node
      start, // Update the index of the node in children
      deleteCount,
      items, // Information about the current node
    };
    if (immediately) {
      this.updateQueue.push(update);
      this.applyUpdate();
    } else {
      // Put it into the update queue to collect the update instruction
      if (this.updateQueue.length === 0) {
        setTimeout((a)= > this.applyUpdate());
      }
      this.updateQueue.push(update);
    }
  }

  applyUpdate() {
    const action = {
      type: 'splice'.payload: this.updateQueue.map(update= > ({
        path: stringPath(update.path),
        start: update.start,
        deleteCount: update.deleteCount,
        item: update.items[0],})),};// Notify the renderer with setData
    this.context.setData({ action });
    this.updateQueue = []; }}Copy the code


The logic is pretty clear, push the node that needs to be updated (including node path, node information) into the update queue, and then trigger setData notification to the renderer process.


In the rendering process, WXS mechanism should be used to restore update instructions to the rendering tree:

/ / render tree
var tree = {
  root: {
    children: [].}};// Apply the directive to the render tree
function reduce(action) {
  switch (action.type) {
    case 'splice':
      for (var i = 0; i < action.payload.length; i += 1) {
        var value = get(tree, action.payload[i].path);
        if (action.payload[i].item) {
          value.splice(
            action.payload[i].start,
            action.payload[i].deleteCount,
            action.payload[i].item
          );
        } else {
          value.splice(action.payload[i].start, action.payload[i].deleteCount);
        }
        set(tree, action.payload[i].path, value);
      }
      return tree;
    default:
      returntree; }}Copy the code


OK, to start rendering, Remax takes the form of a template for rendering:

<wxs src=".. /.. /helper.wxs" module="helper" />
<import src=".. /.. /base.wxml"/>
<template is="REMAX_TPL" data="{{tree: helper.reduce(action)}}" />
Copy the code

Remax generates a template for each component type, dynamically ‘recursively’ rendering the entire tree:

<template name="REMAX_TPL"> <block wx:for="{{tree.root.children}}" wx:key="{{id}}"> <template is="REMAX_TPL_1_CONTAINER"  data="{{i: item}}" /> </block> </template> <wxs module="_h"> module.exports = { v: function(value) { return value ! == undefined ? value : ''; }}; </wxs> <% for (var i = 1; i <= depth; i++) { %> <%var id = i; %> <% for (let component of components) { %> <%- include('./component.ejs', { props: component.props, id: component.id, templateId: id, }) %> <% } %> <template name="REMAX_TPL_<%=id%>_plain-text" data="{{i: i}}"> <block>{{i.text}}</block> </template> <template name="REMAX_TPL_<%=id%>_CONTAINER" data="{{i: i}}"> <template is="{{'REMAX_TPL_<%=id%>_' + i.type}}" data="{{i: i}}" /> </template> <% } %>Copy the code


Limited to the rendering mechanism of applets, the following factors may affect rendering performance:

  • The process of IPC. Update instructions are notified to the renderer process via IPC, and frequent updates may affect performance. This problem also exists in ReactNative, which involves communication between Native and JS engines. That’s where the miniprogram comes inWXSThis type of scenario is used to deal with complex view interaction problems, such as animation. In the futureRemaxYou need to think about that, too
  • ReconcilerThis layer has Diff toRendering processYou might have to do it again and again, right?
  • For template-based scenarios, will local updates result in page-level re-rendering? How does the performance compare to the native custom components of applets?


conclusion

This article uses Remax as an example to show how a React custom renderer works. For Remax, it is still in the development stage, and many features are not perfect yet. As for the performance, the author is not good to comment, you can see the initial benchmark given by the official. Students with ability can participate in code contribution or Issue discussion.

Finally, I would like to thank Bian Liu for his review and suggestions.


Further reading

  • Remax – Build applets using real React
  • What is React Fiber
  • React Fiber architecture and source code
  • Hello World Custom React Renderer – Shailesh – Medium
  • ⚛️👆 Part 1/3 – Beginners Guide to Custom React Renderers. How to build your own renderer from scratch? This series is great
  • Enigma WXS, Uni-App How to use it to dramatically improve performance
  • Uni-app doubles the performance of wechat
  • Talk about the running mechanism of small program