In Preact source code reading (2), we looked at the initialization phase of diFF algorithm, Preact adopted the depth of diff algorithm, from the root node, depth traversal child nodes, complete the comparison and DOM creation, insertion. This chapter intends to analyze the basic functions of setState and, based on this, the process of preact’s update phase.

1. The setState function

SetState is our most frequently used function. In the React framework, we use setState to update data and trigger UI rendering. The setState API is as follows: The updater can be object or functions similar to (state, props).

setState(updater, [callback]);
Copy the code

Preact implements setState as follows:

  • Set the _nextState. If _nextState does not exist, initialize _nextState=assign({}, this.state), nextState is a shallow copy of this.state.
  • Call update(s, this.props) when update is a function. Note that state is passed as _nextState.
  • If update exists, shallow copy to _nextState. Update does not exist. Filter this invalid update.
  • When the current VNOde exists, place the callback in _renderCallbacks and the update in the enqueueRender queue.
Component.prototype.setState = function(update, callback) {
  // If _nextState does not exist, set s= this._nextsat
  let s;
  if (this._nextState ! =null && this._nextState ! = =this.state) {
    s = this._nextState;
  } else {
    s = this._nextState = assign({}, this.state);
  }
  if (typeof update == 'function') {
    update = update(s, this.props);
  }
  if (update) {
    assign(s, update);
  }
  // Skip update if updater function returned null
  if (update == null) return;
  if (this._vnode) {
    if (callback) this._renderCallbacks.push(callback);
    enqueueRender(this); }};Copy the code

As you can see, the setState function is mainly to put the updated state in _nextState and put the update in the render queue. EnqueueRender is mainly responsible for queue management for rendering, such as synchronized merging of setstates and rendering order of multilevel components.

2. Update mechanism

When we write React code, we often do this, and in this code, we update multiple states with setState, so how does Preact render in one update, that’s what enqueueRender does. Let’s first look at the functional execution order of Preact’s enqueueRender.

this.setState({ count: count + 1, }); . this.setState({ pre: pre + 1, });Copy the code

The Preact function call is shown below:

  • SetState call to place the update in the enqueueRender queue.
  • As defer(process), nextTick executes the render queue.
  • Call renderComponent to complete the UI update.

2.1 enqueueRender

The enqueueRender function updates the status of the queue, triggering component updates only if the component has not been updated (_diry=false) and the number of components to be updated is zero. Preact marks the update status of a component with _dirty, and _dirty=true to compare if the current component is in an update, which combines multiple updates of the component into one. Use process_rerenderCount to mark the number of components currently to be updated, and render will not be triggered if it is greater than 1. Preact supports custom updates in the options.deounceRendering way, For example, we can define the options. DebounceRendering = window. RequestAnimationFrame form, define the renewal of the queue.

export function enqueueRender(c) {
  // The component is not updated (_dirty=false) and the component to be updated is 0
  if (
    (!c._dirty &&
      (c._dirty = true) && rerenderQueue.push(c) && ! process._rerenderCount++) ||// Customizes the update mode to trigger UI updatesprevDebounce ! == options.debounceRendering ) {// prevDebounce specifies the custom update modeprevDebounce = options.debounceRendering; (prevDebounce || defer)(process); }}Copy the code

Preact provides an update as defer, which is defined as Promise().then() if the Promise is present and setTimeout if it is not. Defer (Process) means that the process will be executed on the next Eventloop cycle, triggering the diFF, component update.

const defer =
  typeof Promise= ='function'
    ? Promise.prototype.then.bind(Promise.resolve())
    : setTimeout;
Copy the code

2.2 the Process

The function definition of process is as follows, and its specific functions are as follows:

  • Set process._rerenderCount = rerenderQueue.length to mark the number of current renderings.
  • The rerenderQueue is sorted in ascending order based on _depth, with updates starting from the child component and updating from bottom up.
  • The queue loops through, calling renderComponent to complete the update of the component.
function process() {
  let queue;
  while ((process._rerenderCount = rerenderQueue.length)) {
    queue = rerenderQueue.sort((a, b) = > a._vnode._depth - b._vnode._depth);
    rerenderQueue = [];
    queue.some(c= > {
      if (c._dirty) renderComponent(c);
    });
  }
}
process._rerenderCount = 0;
Copy the code

While Process performs updates and resets of queues, Preact performs component updates in a bottom-up manner to complete a cycle of updates.

2.3 renderComponent

RenderComponent is responsible for updating a single component, and its main function is to call diff to complete the node update.

  • Diff is called to complete the diff and DOM update of the node. When parentDom exists, diff is invoked to complete node update and DOM generation.
  • Call commitRoot to complete the _renderCallback call, including the lifecycle and setState Callback.
  • The pointing of the DOM of the parent node has changed. When newDom! = oldDom, for example if/else causes a node change, we need to update the dom pointing.
function renderComponent(component) {
  let vnode = component._vnode,
    oldDom = vnode._dom,
    parentDom = component._parentDom;
  if (parentDom) {
    let commitQueue = [];
    const oldVNode = assign({}, vnode);
    oldVNode._original = oldVNode;
    // Call diff to complete the node update and dom generation
    letnewDom = diff( parentDom, vnode, oldVNode, component._globalContext, parentDom.ownerSVGElement ! = =undefined.null,
      commitQueue,
      oldDom == null ? getDomSibling(vnode) : oldDom
    );
    // The _renderCallbcks call
    commitRoot(commitQueue, vnode);
    // parentDom pointing change
    if(newDom ! = oldDom) { updateParentDomPointers(vnode); }}}Copy the code

The updateParentDomPointers start with the current diff node and change the Pointers to _dom/_component.base upwards.

function updateParentDomPointers(vnode) {
  // If the parent node is not null and is a component, change the point to base.
  if((vnode = vnode._parent) ! =null&& vnode._component ! =null) {
    vnode._dom = vnode._component.base = null;
    for (let i = 0; i < vnode._children.length; i++) {
      let child = vnode._children[i];
      if(child ! =null&& child._dom ! =null) {
        // Point to the first non-null child node
        vnode._dom = vnode._component.base = child._dom;
        break; }}returnupdateParentDomPointers(vnode); }}Copy the code

3. Summary

This chapter mainly analyzes the update mechanism of setState and Preact. Preact setState pushes the components to be updated to the update queue, and updates Vnode and UI of components are completed in NextTick. The asynchronous update mechanism of Preact can effectively reduce the number of rendering times, thus reducing the frequency of DOM update and improving the rendering efficiency of UI.

4. Refer to the documents

  • Github.com/jamiebuilds…
  • Babeljs. IO/docs/en/bab…
  • github.com/babel/babel