preface

First of all, welcome everyone to pay attention to my Nuggets account and Github blog, which can also be regarded as a little encouragement to me. After all, there is no way to make money by writing things, and it is my enthusiasm and everyone’s encouragement that can persist. I’ve shared a few React articles before:

  • React Tech Insider: What does Key bring
  • React Tech Insider: The secrets of setState

In fact, WHEN I read the React source code, IT was really painful. The React code is extremely complex and huge, making it a challenge to read, but it doesn’t stop us from being curious about how React works. A few days ago, some people tried Preact, which basically implemented most of the functions of React with thousands of lines of code. Compared with tens of thousands of lines of code, Preact was quite simple, which opened up another way for us to learn React. This series of articles will focus on the implementation of frameworks such as React. Any inaccuracies are welcome. In the first two articles

  • Preact implements the React framework
  • Preact implements a React framework with diff elements

We looked at element creation and diff algorithms in Preact, which covered some of the components. For a React library, components are probably the most important part of the analysis, because we almost always write components and combine them to form the application we want. Let’s take a look at how components in Preact are implemented from the ground up.

The component rendering

First, let’s look at how the virtual DOM returned by the component is rendered as a real DOM. Let’s look at how Preact’s components are constructed:

//component.js
function Component(props, context) {
    this._dirty = true;

    this.context = context;

    this.props = props;

    this.state = this.state || {};
}

extend(Component.prototype, {

    setState(state, callback) {
        / /...
    },

    forceUpdate(callback) {
        / /...
    },

    render() {}

});Copy the code

We might assume that the Component constructor definition will be extremely complex. In fact, Preact has very little Component definition code. There are only four instance properties of a component:

  • _dirty: Indicates existenceDirty data(that is, the data does not match the existing corresponding rendering), such as multiple calls to a component instancesetState,_dirtyfortrue, but because of this property, the component will only be put into the update queue once.
  • contextComponent:contextattribute
  • propsComponent:propsattribute
  • stateComponent:stateattribute

Using the extends method (which is similar to the extends in ES6 object. assign or Underscore. Js), we create a few methods in the prototype of the component’s constructor:

  • setState: with the ReactsetStateSame, used to update componentsstate
  • forceUpdate: with the ReactforceUpdateSame, at oncesynchronousRerender components
  • render: Returns the virtual DOM of the component’s rendered content, where the function body is empty

So when we write components (Component) class inheritance ponent, preact.Com is only inherit the methods and properties of the above, this to the user, so not only provides and simple API for use, and the most important thing is that we will be the Component internal logic encapsulated, isolated with the user, Avoid unnecessary errors when users inadvertently modify the internal implementation of a component. Remember that the render function provided by Preact calls the internal diff function, which actually calls the idiff function (see article 2 for more details):

As you can see from the above figure, inside the idiff function at the beginning if vNode. nodeName is of type function (function), the function buildComponentFromVNode is called:

function buildComponentFromVNode(dom, vnode, context, mountAll) {
    //block-1
    let c = dom && dom._component,
        originalComponent = c,
        oldDom = dom,
        isDirectOwner = c && dom._componentConstructor===vnode.nodeName,
        isOwner = isDirectOwner,
        props = getNodeProps(vnode);
    //block-2    
    while(c && ! isOwner && (c=c._parentComponent)) { isOwner = c.constructor===vnode.nodeName; }//block-3
    if(c && isOwner && (! mountAll || c._component)) { setComponentProps(c, props, ASYNC_RENDER, context, mountAll); dom = c.base; }else {
    //block-4
        if(originalComponent && ! isDirectOwner) { unmountComponent(originalComponent); dom = oldDom =null;
        }

        c = createComponent(vnode.nodeName, props, context);
        if(dom && ! c.nextBase) { c.nextBase = dom; oldDom =null;
        }
        setComponentProps(c, props, SYNC_RENDER, context, mountAll);
        dom = c.base;

        if(oldDom && dom! ==oldDom) { oldDom._component =null;
            recollectNodeTree(oldDom, false); }}return dom;
}Copy the code

The function buildComponentFromVNode converts the virtual DOM (VNode) representing the component into the real DOM. The parameters are:

  • dom: The actual DOM node corresponding to the component
  • vnode: virtual DOM node of the component
  • context: of a componentcontextattribute
  • mountAll: Indicates that the content of the component needs to be rerendered rather than modified based on the last rendering.

To facilitate analysis, we split the function into several parts and analyze them in turn:

  • First code:domIs the actual DOM node corresponding to the component (or if not renderedundefined),domNodes in the_componentAttributes areComponent instanceThe cache.isDirectOwnerUsed to indicate used to identify the originaldomWhether the component type of the node is the same as that of the current virtual DOM. And then use the functiongetNodePropsTo get the attribute value of the virtual DOM node.
getNodeProps(vnode) {
    let props = extend({}, vnode.attributes);
    props.children = vnode.children;

    let defaultProps = vnode.nodeName.defaultProps;
    if(defaultProps! = =undefined) {
        for (let i in defaultProps) {
            if (props[i]===undefined) { props[i] = defaultProps[i]; }}}return props;
}Copy the code

The logic of the getNodeProps function is uncomplicated. Assign the attributes of vNode and the chidlren attributes to the props, and then if there is a component with defaultProps, Assigns properties that exist for defaultProps and properties that do not exist for props, and returns props.

  • Second code: if the currentdomNode correspondingComponent typeThe component type that corresponds to the current virtual DOMDon’t agreeIs looked up in the parent componentComponent instance of the same type as the virtual DOM node(But maybe not). In fact, this is only for higher-order components, assuming there is an order of higher-order components:
HOC => Component => DOM elementCopy the code

Above HOC represents the higher-order component, returning the component, which then renders the DOM element. In Preact, there is a property identifier between this higher-order component and the returned child component, that is, the _component in the component instance of HOC points to the component instance of Compoent and the _parentComponent attribute of the component instance points to the HOC instance. We know that the _component attribute in the DOM refers to the corresponding component instance. Note that in the above example the _component attribute in the DOM refers to the HOC instance, not the Component instance. If you understand the above section, you can see why this loop exists. The purpose of this loop is to find the higher-order component that renders the DOM in the first place (in case the instance referred to by the DOM’s corresponding _component attribute is modified), and then determine whether the higher-order component is consistent with the current VNode type.

  • Third section of code: If there is a component instance corresponding to the current virtual DOM, the function setComponentProps is directly called, which is equivalent to modifying the rendering based on the component instance, and then the base attribute in the component instance is the latest DOM node.

  • Fourth piece of code: Let’s not focus on the implementation details of a particular function, just focus on the code logic. First, if the previous DOM node corresponds to a component and the virtual DOM corresponds to a different component type, unmount the previous component (unmountComponent). CreateComponent is used to create a component instance of the current virtual DOM. SetComponentProps is used to create a DOM node for the component instance. If the current DOM element is different from the previous DOM element, The previous DOM is retrieved (the recollectNodeTree function was introduced in Diff’s article).

For those of you who have read Preact’s Diff algorithm before, we already know the general rendering process of the component, but we have to go into the internal details of the createComponent and setComponentProps functions to get a deeper understanding of the details.

createComponent

For createComponent, look at the component-recycler.js file:

import { Component } from '.. /component';

const components = {};

export function collectComponent(component) {
    let name = component.constructor.name;
    (components[name] || (components[name] = [])).push(component);
}

export function createComponent(Ctor, props, context) {
    let list = components[Ctor.name],
        inst;

    if (Ctor.prototype && Ctor.prototype.render) {
        inst = new Ctor(props, context);
        Component.call(inst, props, context);
    }
    else {
        inst = new Component(props, context);
        inst.constructor = Ctor;
        inst.render = doRender;
    }

    if (list) {
        for (let i=list.length; i--; ) {
            if (list[i].constructor===Ctor) {
                inst.nextBase = list[i].nextBase;
                list.splice(i, 1);
                break; }}}return inst;
}

function doRender(props, state, context) {
    return this.constructor(props, context);
}Copy the code

The main function of the components variable is to set up a Share Pool to reuse the content rendered by the component. The collectComponent function allows you to recycle a component for later reuse. Through the components in the function collectComponent (component) constructor) name) classification will be reusable components are cached in the buffer pool. The createComponent function creates a component instance. The props and context parameters correspond to the properties and context of the component (same as React), while the Ctor component is the type of the component (function or class) to be created. We know that if our component definition is defined in ES6 as follows:

class App extends Component{}Copy the code

We know that class is just a syntactic sugar, and the above code uses ES5 to implement the equivalent of:

function App(){}
App.prototype = Object.create(Component.prototype, {
    constructor: {
        value: App,
        enumerable: true.writable: true.configurable: true}});Copy the code

If you are not familiar with Object. Create in ES5, it is used for Prototypal Inheritance to create new objects based on existing objects. The first argument to Object.create is the Object to inherit from, and the second argument is the Object to which the new Object defines additional attributes (similar to the argument to Object.defineProperty). If I had to implement a simple object.create function myself, I could write:

function create(prototype, ... obj){
    function F(){}
    F.prototype = prototype;
    return Object.defineProperties(newF(), ... obj); }Copy the code

Now that you know that if your Component inherits the Preact Component, there must be a render method in the prototype, use new to create an inst instance of Ctor (which already contains your custom render function). But if you don’t pass props and context to the parent constructor super, The properties of props and context in the inst are undefined. To initialize the props and context in the inst, call component. call(inst, props, context). If there is no render Function in the Component, the Function is of type PFC(Pure Function Component). The instance is created by calling the function Component directly, with its constructor property set to the function passed in. Since there is no render function in the instance, the doRender function is used as the render property of the instance, and the doRender function returns the virtual DOM returned by Ctor as the result. We then take the instance of the same type of component from the shared pool of component recycling, extract the previously rendered instance (nextBase) from it, and assign it to the nextBase attribute of our newly created component instance. The purpose is to be able to render based on this DOM element with less cost associated rendering.

setComponentProps

function setComponentProps(component, props, opts, context, mountAll) {
    if (component._disable) return;
    component._disable = true;

    if ((component.__ref = props.ref)) delete props.ref;
    if ((component.__key = props.key)) delete props.key;

    if(! component.base || mountAll) {if (component.componentWillMount) component.componentWillMount();
    }
    else if (component.componentWillReceiveProps) {
        component.componentWillReceiveProps(props, context);
    }

    if(context && context! ==component.context) {if(! component.prevContext) component.prevContext = component.context; component.context = context; }if(! component.prevProps) component.prevProps = component.props; component.props = props; component._disable =false;

    if(opts! ==NO_RENDER) {if(opts===SYNC_RENDER || ! component.base) { renderComponent(component, SYNC_RENDER, mountAll); }else{ enqueueRender(component); }}if (component.__ref) component.__ref(component);
}Copy the code

The setComponentProps function is used to set properties for a component instance. The props usually comes from attributes in JSX. The function parameters component, props, context, and mountAll are defined by their names. The opts parameter represents a different refresh mode:

  • NO_RENDER: Do not render
  • SYNC_RENDER: Synchronous rendering
  • FORCE_RENDER: Forces the render to refresh
  • ASYNC_RENDER: Asynchronous rendering

Exit the Component if the _disable attribute is true; otherwise, set the _disable attribute to true, which acts as a lock to ensure atomicity. If ref and key are present in the properties passed to the component, they are cached in __ref and __key of the component, respectively, and removed from the props. Base in the component instance stores the real DOM node corresponding to the previous component instance. If this attribute does not exist, it indicates that it is the first rendering of the component. If the lifecycle function (hook function)componentWillMount is defined in the component, it will be executed here. If not for the first time, if there is a life cycle function componentWillReceiveProps, requires the latest call componentWillReceiveProps props and context as parameters. Then, the current context and props properties are cached in the preContext and prevProps properties of the component, and the context and props properties are updated to the latest context and props. Finally, set the component’s _disable property back to false. If the component is updated with NO_RENDER mode, no rendering is required. If it is SYNC_RENDER or render for the first time (base property is null) then renderComponent is executed, The rest of the cases (such as ASYNC_RENDER, an asynchronous render triggered by setState) execute the function enqueueRender(the enqueueRender function is analyzed at setState). At the end of the function, if a ref function exists, it is called with the component instance as an argument. Preact does not support the ref attribute as a string in React, but this is not important because React itself does not recommend using the ref attribute as a string. It may be abolished in a future release. Next we need to see what the renderComponent function (which is very verbose) and enqueueRender function do:

renderComponent

renderComponent(component, opts, mountAll, isChild) {
    if (component._disable) return;

    let props = component.props,
        state = component.state,
        context = component.context,
        previousProps = component.prevProps || props,
        previousState = component.prevState || state,
        previousContext = component.prevContext || context,
        isUpdate = component.base,
        nextBase = component.nextBase,
        initialBase = isUpdate || nextBase,
        initialChildComponent = component._component,
        skip = false,
        rendered, inst, cbase;
    // block-1
    if (isUpdate) {
        component.props = previousProps;
        component.state = previousState;
        component.context = previousContext;
        if(opts! ==FORCE_RENDER && component.shouldComponentUpdate && component.shouldComponentUpdate(props, state, context) ===false) {
            skip = true;
        }
        else if (component.componentWillUpdate) {
            component.componentWillUpdate(props, state, context);
        }
        component.props = props;
        component.state = state;
        component.context = context;
    }

    component.prevProps = component.prevState = component.prevContext = component.nextBase = null;
    component._dirty = false;
    if(! skip) {// block-2
        rendered = component.render(props, state, context);

        if (component.getChildContext) {
            context = extend(extend({}, context), component.getChildContext());
        }

        let childComponent = rendered && rendered.nodeName,
            toUnmount, base;
        //block-3
        if (typeof childComponent==='function') {
            let childProps = getNodeProps(rendered);
            inst = initialChildComponent;

            if (inst && inst.constructor===childComponent && childProps.key==inst.__key) {
                setComponentProps(inst, childProps, SYNC_RENDER, context, false);
            }
            else {
                toUnmount = inst;

                component._component = inst = createComponent(childComponent, childProps, context);
                inst.nextBase = inst.nextBase || nextBase;
                inst._parentComponent = component;
                setComponentProps(inst, childProps, NO_RENDER, context, false);
                renderComponent(inst, SYNC_RENDER, mountAll, true);
            }

            base = inst.base;
        }
        else {
        //block-4
            cbase = initialBase;

            toUnmount = initialChildComponent;
            if (toUnmount) {
                cbase = component._component = null;
            }

            if (initialBase || opts===SYNC_RENDER) {
                if (cbase) cbase._component = null; base = diff(cbase, rendered, context, mountAll || ! isUpdate, initialBase && initialBase.parentNode,true); }}// block-5
        if(initialBase && base! ==initialBase && inst! ==initialChildComponent) {let baseParent = initialBase.parentNode;
            if(baseParent && base! ==baseParent) { baseParent.replaceChild(base, initialBase);if(! toUnmount) { initialBase._component =null;
                    recollectNodeTree(initialBase, false); }}}if (toUnmount) {
            unmountComponent(toUnmount);
        }

        //block-6
        component.base = base;
        if(base && ! isChild) {let componentRef = component,
                t = component;
            while((t=t._parentComponent)) { (componentRef = t).base = base; } base._component = componentRef; base._componentConstructor = componentRef.constructor; }}//block-7
    if(! isUpdate || mountAll) { mounts.unshift(component); }else if(! skip) {if(component.componentDidUpdate) { component.componentDidUpdate(previousProps, previousState, previousContext); }}if(component._renderCallbacks! =null) {
        while (component._renderCallbacks.length) component._renderCallbacks.pop().call(component);
    }
    //block-8
    if(! diffLevel && ! isChild) flushMounts(); }Copy the code

We split the code into eight sections for ease of reading, but let’s look at the variable declarations at the beginning of the function: the props, context, and state properties of the component instance to be rendered represent the latest component instance properties to be rendered. The corresponding preProps, preContext, and preState represent the properties of the previous state component instance before rendering. The variable isUpdate represents whether the component is currently in the process of updating or rendering (mount). We can judge by whether there is a real DOM node corresponding to the previous component instance. If there is a DOM node, it is considered as the updating process; otherwise, it is considered as the rendering (mount) process. NextBase means that modifications can be made based on this DOM element (either from the last rendering or from a component instance of the same type previously recycled) to minimize the rendering cost. The child components of a component represented by the _component attribute in a component instance exist only if the component returns a component (i.e. the current component is a higher-order component). The skip variable is used to indicate whether updates need to be skipped (for example, the life cycle function shouldComponentUpdate returns false).

  • First code: If component.base is present, the component is in the process of updating its actual DOM element. To replace the props, state, and context with previousProps, previousState, and previousContext, This is because the this.props, this.state, and this.context elements in shouldComponentUpdate and componentWillUpdate are still in the pre-update state. ShouldComponentUpdate = functions (props, state, context); shouldComponentUpdate = functions (props, state, context); If the result returned is false, the refresh process should be skipped, that is, the flag bit skip is set to true. Otherwise, if shouldComponentUpdate returns a value other than false, check whether the life cycle function componentWillUpdate exists and execute if it does. Finally, replace the props, state, and context of the component instance with the latest state, empty the prevProps, prevState, and prevContext properties of the component instance, and set the _dirty property to false. Note that only _dirty is put on the update queue if it is false, and then _dirty is set to true so that component instances are not put on the update queue more than once.

  • If the update process is not skipped (skip false), the second code is executed. Preact executes the render function of the component instance (as opposed to the render function of React, the render function of Preact passes props, state, and context). Rendered returns the rendered virtual DOM element (VNode) of the component instance. If the component has the function getChildContext, it generates the current context to be passed to the child component. We extend the code (the extend ({}, the context), component. GetChildContext ()) as you can see, If the parent component has a context property and the context returned by getChildContext in the current component instance also has the same property, Properties in the context returned by the current component instance getChildContext override the same properties in the parent component’s context.

  • Raster.nodename (rendered. NodeName), the rendered virtual DOM type of the component instance (rendered. NodeName). This Component is a high-order Component. If you don’t know about high-order components, you can poke this article. In the case of higher-order components, the properties of the virtual DOM neutron component are first obtained using the getNodeProps function. If the component has an instance of a childComponent and the child instance constructor is of the same type as the child virtual dom returned by the current component (inst.constructor===childComponent) with the same key (childProps. Key ==inst.__key), Just call setComponentProps recursively in SYNC_RENDER mode to update the property props of the child component. The reason for this is that if the preceding conditions are met, the instances of the child components that are rendered before and after are not changed, only the parameters (props) of the child components that are passed in are changed. The child component only needs to render the real DOM corresponding to the current props. Otherwise, if the constructor of the previous subcomponent instance is different from the virtual DOM type of the subcomponent returned by the current component, or if the two component instances are different based on the key value, the new subcomponent needs to be rendered. Not only do you call createComponent to create instances of child components (createComponent(childComponent, childProps, Context) and sets the correlation for the current child and component (that is, the _component, _parentComponent properties) and uses toUnmount to indicate the component instance to be unloaded. Call setComponentProps to set the ref and key, etc., and call the life cycle functions of the component (e.g., componentWillMount). The next sentence calls renderComponent(inst, SYNC_RENDER, mountAll, true) to render child components synchronously. It’s important to note that the setComponentProps function is not called with SYNC_RENDER, which itself triggers renderComponent to render components. The reason for this is to set isChild to true when calling renerComponent, which we’ll see later. After the renderComponent is called, inst.base is the actual DOM node rendered by our child component.

  • In the fourth code, we deal with the non-component types (that is, ordinary DOM elements) of the virtual DOM types that the current component needs to render. First assignment cbase = initialBase, we know initialBase from initialBase = isUpdate | | nextBase, that is to say if the current is the updated model, then initialBase equals isUpdate, This is what was rendered for the last component. Otherwise, if the component instance has a nextBase(DOM structure from the recycle pool), it can also be modified based on it, with the overall goal of rendering with less cost. If the previous component rendered a function element (component) but now renders a non-function element, assign toUnmount = initialChildComponent to store the component that needs to be unloaded later, and since cbase corresponds to the previous component’s DOM node, Cbase = null is required to render again. Component. _component = null to sever the parent-child relationship between components. After all, no component is returned. If SYNC_RENDER is used, the virtual DOM returned by the component is rendered by calling the idiff function (see the second article diff for details). Let’s look at the parameters and arguments to call the idiff function:

  1. cbaseThe corresponding isdiffthedomArgument that represents the actual DOM before the VNode used for rendering. You can see that if it was a component type, thencbaseA value ofundefinedWe need to start rendering again. Otherwise we can update from the previous render to seek minimal update costs.
    1. renderedThe correspondingdiffIn thevnodeParameter that represents the virtual DOM node to render.
    2. contextThe correspondingdiffIn thecontextParameter representing the component’scontextProperties.
    3. mountAll || ! isUpdateThe corresponding isdiffIn themountAllParameter to re-render the DOM node rather than based on the previous DOM modification,! isUpdateIt’s a non-updated state.
    4. initialBase && initialBase.parentNodeThe corresponding isdiffIn theparentParameter that represents the parent node of the current render node.
    5. diffThe sixth argument to the function iscomponentRootThat argument fortrueRepresents the current momentdiffIn the componentrenderCall the render content of the function, or say that the current render content belongs to the component type.

We know that the IDIff function returns the virtual DOM corresponding to the rendered real DOM node, so the base variable stores the real DOM element rendered by this component.

  • Code Part 5: If the component returns a virtual DOM node before and afterThe corresponding real DOM nodes are different, or the virtual DOM nodes returned before and afterBefore and afterComponent instanceIf not, replace the previous DOM node in the parent DOM element with the currently rendered DOM node (baseParent.replaceChild(base, initialBase)), the function is called if there are no component instances that need to be unloadedrecollectNodeTreeReclaim the DOM node. Otherwise if the previous component rendering isElement of function type, but needs to be discarded, call the functionunmountComponentUnload (call the relevant lifecycle functions).
function unmountComponent(component) {
    let base = component.base;
    component._disable = true;

    if (component.componentWillUnmount) component.componentWillUnmount();

    component.base = null;

    let inner = component._component;
    if (inner) {
        unmountComponent(inner);
    }
    else if (base) {
        if (base[ATTR_KEY] && base[ATTR_KEY].ref) base[ATTR_KEY].ref(null);
        component.nextBase = base;
        removeNode(base);
        collectComponent(component);
        removeChildren(base);
    }
    if (component.__ref) component.__ref(null);
}Copy the code

UnmountComponent sets _disable to true to disable the component, and calls componentWillUnmount if the component has a life cycle. We then recursively call the function unmountComponent to recursively unload the component. If a component renders a DOM node previously and the ref function exists on the outermost node, it is executed with the null argument. DOM elements are then stored in nextBase for recycling. Call removeNode to detach the parent node from the base node. The purpose of the removeChildren function is to recursively traverse all of the child DOM elements and reclaim the nodes (as described in the previous article, involving ref calls to child elements). Finally, if the component itself has a ref attribute, it is called directly with null as an argument.

  • Code Part 6:component.base = baseThe DOM element used to render the current component is stored in the component instancebaseAttribute. Let’s take an example of the following code, if there is the following structure:
HOC1 => HOC2 => Component => DOM elementCopy the code

HOC stands for high-order component, and Component stands for custom component. You’ll notice that the base attributes of HOC1, HOC2, and compoent all refer to the last DOM element, and the _component in the DOM element refers to the group valence instance of HOC1. You can see why there is a loop that assigns the correct base attribute to the parent component and the correct component instance to the DOM node’s _component attribute.

  • In the virtualized seventh section of code, if the current component is not in update mode, the system stores the component in the virtualized queue, or uses the unshift method to store the component in the virtualized queue, or uses the pop method to store the component in the virtualized queue. The convenience of calling components later corresponds to operations like componentDidMount lifecycle functions and others. If the update process is not skipped (skip === false), the component’s corresponding lifecycle function componentDidUpdate is called at this point. Then, if there is a component that has a _renderCallbacks property (a callback function that stores the corresponding setState function, since the setState function is also essentially implemented through renderComponent), pop it up here and execute it.

  • In the eighth code, if diffLevel is 0 and isChild is false, the flushMounts function is executed

function flushMounts() {
    let c;
    while ((c=mounts.pop())) {
        if(c.componentDidMount) c.componentDidMount(); }}Copy the code

“FlushMounts” is also used to select a component instance from the virtualized queue, and the life cycle (componentDidMount) function is executed for the virtualized queue. In fact, those of you who read diff’s previous articles will remember that in the diff function:

function diff(dom, vnode, context, mountAll, parent, componentRoot) {
    / /...
    if(! --diffLevel) {/ /...
        if (!componentRoot) flushMounts();
    }
}Copy the code

There are two calls to flushMounts, one inside the renderComponent and the other inside the diff function. So when do you trigger the next two pieces of code? ComponentRoot () {componentRoot ();}} componentRoot () {componentRoot ();}} If the diffLevel is zero, the call to flushMounts will be called. If the diffLevel is zero, the call to flushMounts will be used. The flushMounts function is called if a component that has already been rendered causes a re-rendering using setState or forceUpdate and causes the child to create a new instance (for example, when a different component type is returned twice).

setState

State is an extremely important part of Preact’s components. The API involved is setState, defined in the Component prototype so that all custom Component instances that inherit from Component can reference setState.

extend(Component.prototype,{
    / /...

    setState(state, callback) {
        let s = this.state;
        if (!this.prevState) this.prevState = extend({}, s);
        extend(s, typeof... the = = ='function' ? state(s, this.props) : state);
        if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback);
        enqueueRender(this);
    }

    / /...
});Copy the code

First we see that setState takes two arguments: the new state and the updated state callback function, where state can be either a partial object of an object type or a function type. We first use the extend function to generate a copy of the current state, prevState, and store the state of the previous state. Then, if state is a function, the generated value of the function is overwritten into state; otherwise, the new state is overwritten directly into state, and this.state becomes the new state. If the second parameter callback exists in setState, it is stored in the instance attribute _renderCallbacks(if the _renderCallbacks attribute does not exist, it needs to be initialized). The function enqueueRender is then executed.

enqueueRender

Let’s take a look at the magic enqueueRender function:

let items = [];

function enqueueRender(component) {
    if(! component._dirty && (component._dirty =true) && items.push(component) == 1) { defer(rerender); }}function rerender() {
    let p, list = items;
    items = [];
    while ((p = list.pop())) {
        if(p._dirty) renderComponent(p); }}const defer = typeof Promise= ='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;Copy the code

We can see that when the _dirty attribute in the component instance is false, the attribute _dirty is set to true and placed in items. The function rerender is deferred asynchronously when the update queue is first entered by items. This deferred asynchronous function, in browsers that support Promises, uses promise.resolve ().then, otherwise uses setTimeout. Rerender simply fetches the components in the items to be updated one by one and executes renderComponent on them. RenderComponent opt is passed to ASYNC_RENDER instead of ASYNC_RENDER, there is no difference between the two. The only caveat:

/ / renderComponent inside
if (initialBase || opts===SYNC_RENDER) {
    base = diff(/ /... ;
}Copy the code

We must be performing diff during rendering, which means initialBase must be a non-false value, which is guaranteed.

initialBase = isUpdate || nextBaseCopy the code

IsUpdate = component.base and component.base must exist and be the content of the last rendering. You might wonder what happens if the last time the component render function returned NULL? Actually, those of you who read the second article know that inside the IDIff function

if (vnode==null || typeof vnode==='boolean') vnode = ' ';Copy the code

Even if render returns null, it will be treated as an empty Text and will render to the Text type in the DOM.

  

forceUpdate

extend(Component.prototype,{
    / /...

    forceUpdate(callback) {
        if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback);
        renderComponent(this, FORCE_RENDER);
    }

    / /...
});Copy the code

All you need to do to execute forceUpdate is put the callback function into the component instance’s _renderCallbacks property and call the function renderComponent to force a refresh of the current component. Note that the FORCE_RENDER mode we are using is a FORCE_RENDER force refresh. The difference is that we don’t need to use shouldComponentUpdate to refresh.

conclusion

Now that we’ve looked at the component-related code in Preact, I probably haven’t covered every scenario, but I’ve tried to cover all the relevant parts. The code is relatively long and often painful to look at, sometimes having to go back several times to figure out the parts of a variable. But you will find that as you read it repeatedly and carefully, the meaning of the code will gradually become clear. A hundred times reading a book makes sense, but the same is true of code. If the article has incorrect place, welcome to point out, study together.