Read the instructions

  • The preact version referenced for this article is 10.5.13
  • The article will omit most of the logic, such as hydrating, context, isSvg, etc. So be careful if any big shots come in.
  • Abbreviated version code

An overview of the diff

  • For components (function or class components) : Execute the lifecycle of the component (function components are wrapped as class components) and call diffChildren to continue the recursion.
  • For native DOM nodes: Call diffElementNodes.
export function diff ( parentDom, newVNode, oldVNode, commitQueue, oldDom, ) { const newType = newVNode.type; try { let c, isNew, oldProps, oldState; let newProps = newVNode.props; outer: if (typeof newType == 'function') { // 1. Check whether to initialize // 2. Execute the corresponding life cycle // 3. Continue to compare child} // native node processing else {}}Copy the code

Component handles

Check whether initialization is performed

  • Oldvnode. _component to determine whether initialization is complete,
    • Initialization is done directly using the old Preact instance.
    • Initialization not complete: Component instantiation is complete.Flag isNew = true, the life cycle of subsequent initialization.Function components and fragments are encapsulated as class components.
      • How to do it: Instantiate an empty class component and execute the function component in the render function of the class component, which is why the function component is re-executed every time it is updated, because the Render function is re-called every time it is updated.
      • Benefits: Logical unification of function components and class components.
// Initialize completed: call vnode._component (React Component instance) If (oldvNode. _component) {newvNode. _component = c = oldvNode. _component; } else {// Class Component, prototype and render; Non-class Component if ('prototype' in newType && newType.prototype.render) {newvNode. _component = c = new newType(newProps); } else {// A function Component or Fragment instantiates a Component from a class Component. render = Fragment => (return this.props.children) newVNode._component = c = new Component(newProps); c.constructor = newType; c.render = doRender; } // initialize data.props = newProps; c.state = c.state || {}; c._renderCallbacks = []; isNew = c._dirty = true; // _dirty is used for batch update}Copy the code
  • A component is essentially a prototype chain. The state and props used at ordinary times are all on the prototype chain

Execution life cycle

  • Preact supports the life cycle (componentWillMount, componentWilReceiveProps), which is called based on whether getDerivedStateFromProps is defined

Changes in data

During the lifecycle execution, there are three states: oldState, state, _nextState, oldProps, props, and newProps. Respectively oldState data available to render, latest data (_nextState).

  • C. state and c. trops, c._nextState are component properties, and the rest are variables. The main reason is that lifecycle functions can get props and state for different states.
    • Because some lifecycle execution changes the state and props, resulting in data changes. And there are life cycles where you need to get the latest data, and there are life cycles where you need to get the old data. So you need to back up one copy for the latest data and one copy for old data. Render assigns the latest data to the component properties (c.state, c.props) before executing.
    / / a copy as the latest data, a data as old c. _nextState = c. _nextState | | Object. The assign ({}, c.s. Tate); oldProps = c.props; oldState = c.state; // Perform the life cycle before render // assign the latest state to the component c.props = newProps; c.state = c._nextState; c._vnode = newVNode; c._parentDom = parentDom; / / render executionCopy the code

Lifecycle execution

  • ComponentDidMount (), componentDidUpdate (), componentDidMount (), componentDidUpdate ()) These are stored in the component’s _renderCallbacks array. Wait until the component finishes executing, then execute the commitQueue for the rest of the life cycle.
/ / implementation life cycle if (newType. GetDerivedStateFromProps) {Object. The assign (c. _nextState, newType.getDerivedStateFromProps(newProps, c._nextState), ) } if (isNew) { if (c.componentDidMount) { c._renderCallbacks.push(c.componentDidMount); } } else { if ( ! c._force && c.shouldComponentUpdate && c.shouldComponentUpdate(newProps, c._nextState) === false ) { c.props = newProps;  c.state = c._nextState; c._vnode = newVNode; newVNode._dom = oldVNode._dom; newVNode._children = oldVNode._children; newVNode._children.forEach(vnode => { if (vnode) vnode._parent = newVNode; }); if (c._renderCallbacks.length) { commitQueue.push(c); } break outer; } if (c.componentDidUpdate) { c._renderCallbacks.push(() => { c.componentDidUpdate(oldProps, oldState); }); } } let tmp = c.render(c.props, c.state); if (! isNew && c.getSnapshotBeforeUpdate) { c.getSnapshotBeforeUpdate(oldProps, oldState); }Copy the code

Contrast child node

let isTopLevelFragment = tmp && tmp.type === Fragment && tmp.key == null; let renderResult = isTopLevelFragment ? tmp.props.children : tmp; // diffChildren(parentDom, array. isArray(renderResult)? renderResult : [renderResult], newVNode, oldVNode, commitQueue, oldDom, ); if (c._renderCallbacks.length) { commitQueue.push(c); } c._dirty = false; c._force = false;Copy the code

Native node processing

newVNode._dom = diffElementNodes(
    oldVNode._dom,
    newVNode,
    oldVNode,
    excessDomChildren,
    commitQueue,
);
Copy the code