This series examines the implementation of Preact10.4.6, including lifecycle, diff, update mechanism, hooks, and exception handling. This section looks at mapping and execution of the Render functionality, initialization phase, and lifecycle. You may need to know about the JSX to VNo transformation, as shown in the following article.

  • Preact Source reading (zero) – JSX to VDOM conversion

1. App conversion

For the render function, is our common way of writing, but it in the actual content of the product that is actually translated into virtual VNode nodes.

import { createElement, render, Component, Fragment } from 'preact';
class App extends Component {}
render(<App />, document.body);
Copy the code

For example, in a demo, the WebPack product, render(, document.body), is converted to the following form

Object(preact__WEBPACK_IMPORTED_MODULE_0__["render"]) // Render call // createElement call (Object(preact__WEBPACK_IMPORTED_MODULE_0__["createElement"])(App, null), // mount node document.body);Copy the code

Therefore, the call to createElement(App, null) generates the VNode DOM tree. For a more detailed analysis, see the Preact source read (zero) -JSX to VDOM conversion. CreateElement (App, null) only completes the vnode dom tree of the App. When does the App render function call the vnode? Let’s look at how subsequent calls to Render complete the initialization and lifecycle mapping.

2. Life cycle mapping

2.1 render/hybrate

Before we look at the render function, let’s look at the render/hybrate distinction. In contrast to Render, hybrate’s function is “Hydrate describes how ReactDOM reuses content rendered by the ReactDOMServer server with as much structure as possible, and complicates client-specific content such as event bindings.” The application of Hybrate is the SSR scene, where the Server inserts the first frame of HTML into the root node, so that the first frame, we only need to improve the event binding and so on. For example, in preact 10+, preact uses preact-render-to-string to convert the content into a string, which is inserted into the root node, thereby reuse the HTML structure and content and improve the rendering speed.

2.2 Initialization Process

As shown in the figure below, it is the function call graph of Preact Render. Diff, Diff, diffChildren and diffElementNodes are called recursively in the initialization phase to complete the construction of the whole VDOM tree and the transformation from VDOM tree to DOM.

This section mainly analyzes the mapping of Render, diff and the initialization phase life cycle, and the specific DIFF algorithm will be analyzed in depth in the following chapters.

2.2.1 render

The render function simply inserts a Preact virtual Node into an Element. For example, the demo, render(, document.body) in 1 is the VNode converted into dom and inserted into document.body. The parameter of render has three vnode, parentDom, and replaceNode. Its specific functions are as follows:

  • Vnode. Virtual nodes waiting to be inserted.
  • ParentDom. The parent (Dom Element) to be inserted.
  • ReplaceNode. Specifies the virtual node to be replaced.

The processing steps of the render function can be divided into three parts. For detailed functional analysis, see the note:

// Debug /test to check the validity of vnode/parentDom. For details, see debug/ SRC /debug if (options._root) options._root(vnode, parentDom). Render (vnode, parentoDom, IS_HYDRATE); // Render (vnode, parentoDom, IS_HYDRATE); let isHydrating = replaceNode === IS_HYDRATE; // oldNode = null; // oldNode = null; (replaceNode && replaceNode._children) || parentDom._children let oldVNode = isHydrating ? null : (replaceNode && replaceNode._children) || parentDom._children; Vnode = createElement(Fragment, null, [vnode]); SetState callback let commitQueue = []; setState callback let commitQueue = []; Diff (parentDom, ((isHydrating? parentDom : replaceNode || parentDom)._children = vnode), oldVNode || EMPTY_OBJ, EMPTY_OBJ, parentDom.ownerSVGElement ! == undefined, replaceNode && ! isHydrating ? [replaceNode] : oldVNode ? null : parentDom.childNodes.length ? EMPTY_ARR.slice.call(parentDom.childNodes) : null, commitQueue, replaceNode || EMPTY_OBJ, isHydrating ); Callback commitRoot(commitQueue, vnode); Export function commitRoot(commitQueue, root) {// _commit hooks, See hooks/ SRC /index if (options._commit) {options._commit(root, commitQueue); Some (c => {try {commitQueue = commitQueue = commitQueue = commitQueue = commitQueue = commitQueue = commitQueue = commitQueue = commitQueue = commitQueue  c._renderCallbacks; c._renderCallbacks = []; commitQueue.some(cb => { cb.call(c); }); } catch (e) { options._catchError(e, c._vnode); }}); }Copy the code

2.2.2 the diff

The diff function, a key function of the Preact framework, is used to compare two virtual nodes and insert changes into a Dom Element. Its function takes the following parameters:

export function diff(
    parentDom,
    newVNode,
    oldVNode,
    globalContext,
    isSvg,
    excessDomChildren,
    commitQueue,
    oldDom,
    isHydrating
) {};
Copy the code

The diff function is divided into two types based on the vNode type:

  • Function/Class Component. Complete the call of component life cycle, and call diffChildren to complete the diff of children.
  • Tag elements (div/h1/… , etc.). Call diffElementNodes to complete the diff of the node.

2.2.2.1 Parameter initialization

Diff defines temporary variables and executes _diff Hooks before executing the core code.

// Define the TMP temporary variable, newType let TMP, newType = newvNode.type; // prevent json-injecttion injection if (newvNode.constructor! == undefined) return null; If ((TMP = options._diff)) TMP (newVNode); Copy the codeCopy the code

2.2.2.2 Component processing

Diff’s core processing of components, including lifecycle mapping and diffChildren invocation, is as follows:

// 初始化相关变量
let c, isNew, oldProps, oldState, snapshot, clearProcessingException;
let newProps = newVNode.props;

//context处理,放到后面context分析时介绍
tmp = newType.contextType;
let provider = tmp && globalContext[tmp._id];
let componentContext = tmp
    ? provider
        ? provider.props.value
        : tmp._defaultValue
    : globalContext;// 分为两种场景

// 初始化已完成:调用vnode._component(React Component组件实例)
// 初始化未完成:完成React组件初始化
if (oldVNode._component) {
    c = newVNode._component = oldVNode._component;
    clearProcessingException = c._processingException = c._pendingError;
} else {
  // 两类组件处理
  // Class Component, prototype和render存在时
  // Function Component, 非Class Component
  if ('prototype' in newType && newType.prototype.render) {
     // new Class Component得到Preact Component实例
     newVNode._component = c = new newType(newProps, componentContext);
  } else {
    // Function Component,new Component完成组件的实例化
    newVNode._component = c = new Component(newProps, componentContext);
    // 设置constructor为Function Component
    // render为{ return this.constructor(props, context); }
    c.constructor = newType;
    c.render = doRender;
  }
  // context 发不事件
  if (provider) provider.sub(c);
  // 设置当前props为传入props
  c.props = newProps;
  // 初始化state
  if (!c.state) c.state = {};
  // 设置context
  c.context = componentContext;
  // 设置全局context
  c._globalContext = globalContext;
  // 设置首次更新、更新为true
  isNew = c._dirty = true;
  // 设置render回调,render结束后调用
  c._renderCallbacks = [];
}

// 触发getDerivedStateFromProps生命周期
// 初始化_nextState
if (c._nextState == null) {
    c._nextState = c.state;
}

// getDerivedStateFromProps存在时,执行getDerivedStateFromProps生命周期
if (newType.getDerivedStateFromProps != null) {
    // 两者相等时,浅拷贝c.state
    if (c._nextState == c.state) {
        c._nextState = assign({}, c._nextState);
    }
    // getDerivedStateFromProps得到的state浅拷贝到_nextState
    assign(
        c._nextState,
        newType.getDerivedStateFromProps(newProps, c._nextState)
    );
}

// 获取oldProps/oldState
oldProps = c.props;
oldState = c.state;
// 分为两种:
// 首次触发时,componentWillMount/componentDidMount
// 非首次触发,componentWillReceiveProps/shouldComponentUpdate触发
if (isNew) {
    // getDerivedStateFromProps不存在且componentWillMount存在时调用
    if (
        newType.getDerivedStateFromProps == null &&
        c.componentWillMount != null
    ) {
        c.componentWillMount();
    }
    // componentDidMount存在时,放入c._renderCallbacks
    if (c.componentDidMount != null) {
        c._renderCallbacks.push(c.componentDidMount);
    }
} else {
    // getDerivedStateFromProps不存在且newProps不等于oldProps且componentWillReceiveProps存在
    if (
        newType.getDerivedStateFromProps == null &&
        newProps !== oldProps &&
        c.componentWillReceiveProps != null
    ) {
        c.componentWillReceiveProps(newProps, componentContext);
    }

    // 两种状态不需要diff更新:
    // 非强制更新且shouldComponentUpdate存在且shouldComponentUpdate()返回false
    // 新旧虚拟节点_original相等
    if (
        (!c._force &&
            c.shouldComponentUpdate != null &&
            c.shouldComponentUpdate(
                newProps,
                c._nextState,
                componentContext
            ) === false) ||
        newVNode._original === oldVNode._original
    ) {
        // 更新Props/State
        c.props = newProps;
        c.state = c._nextState;

        // https://gist.github.com/JoviDeCroock/bec5f2ce93544d2e6070ef8e0036e4e8
        if (newVNode._original !== oldVNode._original) c._dirty = false;

        // 更新_vnode/_dom/_children
        c._vnode = newVNode;
        newVNode._dom = oldVNode._dom;
        newVNode._children = oldVNode._children;

        // _renderCallbacks不为空时,push进回调数组
        if (c._renderCallbacks.length) {
            commitQueue.push(c);
        }

        // children re order
        reorderChildren(newVNode, oldDom, parentDom);
        break outer;
    }

    // componentWillUpdate存在时调用
    if (c.componentWillUpdate != null) {
        c.componentWillUpdate(newProps, c._nextState, componentContext);
    }

    // componentDidUpdate存在时,push进去_renderCallbacks
    if (c.componentDidUpdate != null) {
        c._renderCallbacks.push(() => {
            c.componentDidUpdate(oldProps, oldState, snapshot);
        });
    }
}

// render前调用与更新, context、props、state
c.context = componentContext;
c.props = newProps;
c.state = c._nextState;

// _render hooks, 开发者可注册
if ((tmp = options._render)) tmp(newVNode);

// render更新_dirty/vnode/_parentDom
c._dirty = false;
c._vnode = newVNode;
c._parentDom = parentDom;

// 调用render生成新的vnode节点
tmp = c.render(c.props, c.state, c.context);
// 更新state为nextState
c.state = c._nextState;

// childContext,后续context分析时调用
if (c.getChildContext != null) {
    globalContext = assign(assign({}, globalContext), c.getChildContext());
}

// getSnapshotBeforeUpdate生命周期调用
if (!isNew && c.getSnapshotBeforeUpdate != null) {
    snapshot = c.getSnapshotBeforeUpdate(oldProps, oldState);
}

// 是否为根节点(root节点)
let isTopLevelFragment =
    tmp != null && tmp.type == Fragment && tmp.key == null;

// 根Fragment节点,返回props.children,否则返回tmp。
let renderResult = isTopLevelFragment ? tmp.props.children : tmp;// 递归children的处理
diffChildren(
    parentDom,
    Array.isArray(renderResult) ? renderResult : [renderResult],
    newVNode,
    oldVNode,
    globalContext,
    isSvg,
    excessDomChildren,
    commitQueue,
    oldDom,
    isHydrating
);

// c._base等于_dom
c.base = newVNode._dom;

// _renderCallbacks不为空时,push commitQueue队列
if (c._renderCallbacks.length) {
    commitQueue.push(c);
}
// 清空error/exception
if (clearProcessingException) {
    c._pendingError = c._processingException = null;
}

// 设置强制更新为false
c._force = false;
Copy the code

Diff function has completed the mapping and invocation of the React Component lifecycle. Depending on whether it is instantiated, it can be divided into the following two scenarios:

  • First call. To complete React Component instantiation, go through the old and new life cycle depending on whether there are getDerivedStateFromProps.

  • The second call. Update_component, depending on whether getDerivedStateFromProps is available, goes through the new and old lifecycle update process.

The diff function, in addition to the component lifecycle mapping, is the call to Render and the diffChildren function. The call to render, for Function Component, defines constructor = newType; c.render = doRender; Call the function directly to get Vnode. For the Class Component function, call the Class Component render function directly to get vNode.

tmp = c.render(c.props, c.state, c.context); . diffChildren(...) ;Copy the code

DiffChildren deals with children’s VDOM diff and life cycle calls. DiffChilren calls diff to process each child’s diff. In fact, it completes the diff process of components and labels. The subsequent diff process will be analyzed in detail in vDOM Diff algorithm. Take React new lifecycle initialization phase as an example to briefly introduce the lifecycle execution sequence of parent and child components.

  • The lifecycle before Render is called at diff runtime, so the order of execution before render for parent components is parent -> child.

  • The life cycle after render will be placed in the commitQueue queue, and will be entered in the form of child and parent, so when commitRoot calls some, the life cycle after render will be child -> parent.

2.2.2.3 Label element processing

Under non-component type elements, Preact is handled in two scenarios:

  • If the parentDom child node is null and _original is the same, you can directly assign a value without going through the DIFF process.

  • The normal diFF process for a tag element uses diffElementNodes to diff a tag element.

    // If the parentDom child node is null and _original is equal, the diff process is not performed. If (excessDomChildren == null && newvNode._original === oldvNode._original) {newVNode._children = oldVNode._children; newVNode._dom = oldVNode._dom; } else {// tag element diff procedure, diffElementNodes newVNode._dom = diffElementNodes( oldVNode._dom, newVNode, oldVNode, globalContext, isSvg, excessDomChildren, commitQueue, isHydrating ); }

3. Summary

This section mainly introduces the functions of Render, diff, commitRoot, and focuses on the React component’s instantiation, life cycle mapping, and execution sequence analysis.

  • The diff Function instantiates the Class/Function Component based on isNew.
  • Component lifecycle is divided into two types: before render, diff is called; after render, after the overall diff is completed, commitQueue is executed to complete the subsequent lifecycle execution.
  • The life cycle of the parent and child components is executed in the order before parent render- before child render- child render- after child render- after parent render. The next section focuses on Preact’s overall DIFF process and vNode to HTML conversion.

Make a small advertisement, the team continues to expand, welcome each student to deliver. You can send them as follows:

  • Delivery (job.toutiao.com/s/JMpw6Rr) by the following link.
  • You can send your resume to my email ([email protected]) and I will deliver it to you.

4. Refer to the documents

  • The React website
  • preactjs
  • Preact source code analysis
  • Preact source code analysis