React understand the basic principles of the assembly and hooks from Preact

React code base is now relatively large, plus Fiber reconstruction v16 beginners very easy to fall into the sea details, get to know the people will feel that they are Niubi, do not understand it is easy to lose confidence, doubt whether it should continue to engage the front. So try to get back a little confidence in this article right here (master detour).

React Preact is an abbreviated version, the volume is very small, but perfectly formed. If you want to understand the basic principles React, you can go to learn Preact source code, which is the purpose of this article.

Excellent article on React principle has been very much, this is old wine into new bottles, be your own little summary, but also as a foreshadowing later article about it.

Lengthy article, reading time about 20min, mainly occupied by the code, while also painted with the flow chart to understand the code.

Note: The code has been simplified, ignored svg, replaceNode, context and other characteristics of paper-based code v10 version Preact

Virtual-DOM
from createElement start
Component implementation
diff algorithms
diffChildren
diff
diffElementNodes
diffProps
Hooks implementation
useState
useEffect
technology map
expansion

Virtual-DOM

Virtual-DOM is actually an object tree, nothing special, the object tree eventually be mapped to graphical objects. Virtual-DOM is its more central diff algorithms.

You can imagine there is a DOM mapper, see to know the name of justice, the 'DOM mapper' job is to DOM Virtual-DOM object tree map browser page, just in order to improve the DOM 'operational performance'. It is not each time the full amount to render the whole Virtual-DOM tree, but receives support two Virtual-DOM object tree (a pre-update, a later update) calculate the difference between two Virtual-DOM tree by diff algorithms place, and then only apply these local differences to the actual DOM tree, thereby reducing the cost of DOM changes.

Virtual-DOM is more controversial, recommended reading "Online said operating DOM real slow, but the test results than React faster, and why? . " Remember Never leave the scene to judge the quality of a technology. How to React had blown Niubi online, some of the white will feel it hanging Virtual-DOM, JQuery weak burst.

I think not two comparable, from a performance point of view, no matter how fast hardware framework it is also required to operate a native DOM, and it may not have your manual operation using JQuery DOM is more 'fine' framework irrational use may also appear to modify a small state, resulting in an avalanche situation rendering (large-scale re-rendered); and empathy JQuery DOM Although you can operate fine, but unreasonable DOM update policy may also become a performance bottleneck in the application so the key will have to see how you use. .

Why you need Virtual-DOM?

My personal understanding is to liberate the productive forces. Today's hardware performance is getting better, web applications are more complex, but also to keep up productivity Although manual operation DOM may be able to achieve higher performance and flexibility, so for most developers too inefficient, and we may be acceptable to sacrifice a little performance in exchange for greater development efficiency.

So the greater significance of Virtual-DOM is to change the way of development: declarative, data-driven, so that developers do not need to be concerned about the operational details of the DOM (property operation, event binding, DOM node change), that the development and application of way into a view = f (state), which is to liberate the productive forces of a great role in promoting.

Virtual-DOM certainly not the only, nor the first such solution, such as AngularJS, Vue1.x these template-based implementation, it can be said to achieve this transformation of the mode of development. That relative to their Virtual-DOM selling points higher performance is possible, the other Virtual-DOM more thoroughly in the top layer is rendered abstract no longer coupled to the DOM itself, such as can render ReactNative, PDF, and so the terminal UI.

CreateElement from the beginning
a lot of white will JSX equivalent to Virtual-DOM, and in fact the two are not directly related, but we know JSX is a syntax sugar.

E.g. <a href="/"> <span> Home </ span> </a> eventually converted to h ( 'a', {href: '/'}, h ( 'span', null, 'Home' )) this form, h is JSX Element factory method.

h convention under React is React.createElement, and most of Virtual-DOM framework is used h. createElement h is an alias, Vue ecosystem also use this convention, why did not make specific fine (relatively brief?).

You can use @jsx babel annotations or configuration item to configure factory JSX:

/**

  • H @jsx
    * /
    the render (<div> JSX the Hello </ div>, EL);
    Copy the code
    article is not React or Preact introductory article, so go beyond that, more content can check out the official tutorial.

Now take a look createElement, createElement but is to construct an object (VNode):

// ⚛️type node type, there DOM element (string) and custom components, and Fragment, indicates a text node is null
Export function the createElement (type, The props, Children) {
props.children = Children;
// ⚛️ application defaultProps
IF (!! = null && type.defaultProps type = null)
for (in the let type.defaultProps I)
IF (The props [I] === undefined) The props [I] = type.defaultProps [I];
the let REF = The props .ref;
the let Key = props.key;
// ...
// construct VNode objects ⚛️
return createVNode (type, The props, Key, REF);
}

createVNode function Export (type, The props, Key, REF) {
return {type, The props, Key, REF, / ... to omit some built-in fields / constructor: undefined};
}
copy the code
by JSX and assembly, complex objects can be constructed tree:

the render (
<className = div "Container">
<The SideBar />
<Body />
</ div>,
the root,
);
duplicated code

Component implementation
for a view of the frame, the assembly is its soul, as a function of in functional languages, class of complex applications in object-oriented language, no components can not make up.

Component-based thinking is recommended that an application to divide and conquer, splitting and combination of different levels of components, which can simplify the development and maintenance of applications, so that the program be better understood from the technical point of view component is the element type a custom, declare components input (the props), and has its own life cycle state, and methods, the final output Virtual-DOM object tree, as the application of a Virtual-DOM tree branch presence.

Preact custom component is based on the Component class implements the most basic is the state of maintenance of the components, this is achieved by setState.:

function Component(props, context) {}

// ⚛️setState achieve
Component.prototype.setState = function (Update, the callback) {
// clone time rendering State, _nextState life cycle will be used in some manner (e.g. shouldComponentUpdate)
the let S = (this._nextState! = this.state && this._nextState =) ||
(this._nextState = ASSIGN ({}, this.state));

// state更新
if (typeof update !== 'function' || (update = update(s, this.props)))
assign(s, update);

if (this._vnode) {// mounted
// push rendering callback queue, the call volume rendering is complete
IF (the callback) this._renderCallbacks.push (the callback);
// put the asynchronous scheduling queue
enqueueRender (this) ;
}
};
copy the code

enqueueRender the component into an asynchronous batch execution queue, so you can merge setState frequent calls to achieve is very simple:

Q = the let [];
// asynchronous scheduler for asynchronous execution of a callback
const = typeof the defer Promise == 'function'
Promise.prototype.then.bind (Promise.resolve ()) // Micro Task?
: the setTimeout; // callback to setTimeout

enqueueRender function (C) {
// do not need to repeat the push queue has Component
IF (c._dirty && (= c._dirty to true) && q.push (C) ===. 1!)
the defer (Process); / / when the queue is non-empty from empty to start scheduling
}

// batch queue is empty, the call Component forceUpdate
function Process () {
the let P;
? // ordering queue, the priority update from the lower component
q.sort ((a, b) = > b._depth - a._depth);
the while ((P = q.pop ()))
IF (p._dirty) p.forceUpdate (to false); // to false indicates not to update, i.e., do not ignore shouldComponentUpdate
}
copy the code

Ok, the above code can be seen on setState essentially calls forceUpdate be re-rendering component, and from under 'em realize forceUpdate of.

Here Ignoring diff, diff will be treated as a black box, he is a DOM mapper, as stated above diff receiving two VNode tree, as well as a DOM mount point, it will be created during the alignment, the shift in addition or update components and DOM elements, triggering the corresponding life-cycle approach.

Component.prototype.forceUpdate = function (callback) {// callback is placed after the completion of rendering callback
let vnode = this._vnode, dom = this._vnode._dom , parentDom = this._parentDom;

if (parentDom) {// mounted over
const = Force == the callback to false;!
the let mounts = [];
// current component diff call is re-rendered alignments and Virtual-DOM
// ⚛️ Ignoring these parameters, diff will be treated as a black box, he is a DOM mapper,
dom = diff (parentDom, the vnode, the vnode, mounts, this._ancestorComponent, Force, dom);
! IF (dom = null && dom.parentNode == parentDom! )
parentDom.appendChild (DOM);
commitRoot (mounts, the vnode);
}
IF (the callback) the callback ();
};
copy the code

In the render method look, achieved with almost forceUpdate, all call DOM diff algorithms to perform the update, just specify a DOM container from the outside:

// 简化版
export function render(vnode, parentDom) {
vnode = createElement(Fragment, null, [vnode]);
parentDom.childNodes.forEach(i => i.remove())
let mounts = [];
diffChildren(parentDom, null oldVNode, mounts, vnode, EMPTY_OBJ);
commitRoot(mounts, vnode);
}
复制代码

Comb the above process:

So far not seen components of other functions, such as initialization, life cycle functions. These characteristics are defined in the diff function, that is called the mount assembly process or update. The next section will introduce diff

diff algorithm
awaited, it can be seen by the above, and the createElement Component logic are thin, mainly concentrated in the logic function diff. React this process is called the Reconciliation, referred to in Differantiate in Preact.

In order to simplify the implementation of the program Preact DOM and mixed together with diff, but the logic is clear, look to know the directory structure:

the src / the diff
├── than the children array # children.js
two nodes ├── index.js # than
└── props.js # than two props DOM node
copy the code

Before delving into the diff program, look at the basic object structure, easy to understand program flow behind the first look at the appearance of VNode:

type ComponentFactory<P> = preact.ComponentClass<P> | FunctionalComponent<P>;

vnode interface <{P} => {
// node type, built DOM element type string, and the custom component is Component type, Preact component is only a function of the specific type of Component
type: string | ComponentFactory <P> | null;
The props: P {& Children: ComponentChildren} | String | Number | null;
Key: Key
REF: Ref <the any> | null;

/**

  • Internal cache information
    * /
    // child node vnode
    _children: the Array <vnode> | null;
    the DOM node // associated, is the first child for Fragment
    _dom: PreactElement | the Text | null;
    // Fragment, or component returns Fragment last child node of the DOM,
    _lastDomChild: PreactElement | the Text | null;
    // the Component example
    _component: the Component | null;
    }
    copy the code

diffChildren
start with the most simple beginning, above have guessed diffChildren for more than two VNode list.

As shown above, there is first a need to maintain a variable indicating the current position of the insertion oldDOM, which points to the beginning of the first element of the DOM childrenNode, updated or inserted behind each insert newDOM, will point to the next sibling element newDOM.

In traversing the list newChildren process, we will try to find old VNode the same key, and diff if it were new and the old VNode VNode position is not the same, which need to move them; for new DOM, if the insertion position (oldDOM) has come to the end, then directly added to the parent, before oldDOM otherwise inserted into.

Finally Uninstall VNode old VNode list unused.

Details to look at the source:

diffChildren function Export (
parentDom, // Children parent DOM element
newParentVNode, // children of the new parent the vnode
oldParentVNode, // Children old father VNode, diffChildren important than these two Vnode of Children
mounts, // save in the examples of the component to be mounted over the process, after the alignment, trigger componentDidMount lifecycle functions of these components
ancestorComponent, // children's immediate parent 'assembly', i.e., the rendering component instance (render) vNode of
oldDom, // currently mounted DOM, for diffChildren is, oldDom a start point to a first child node
) {
the let newChildren = newParentVNode._children || toChildArray (newParentVNode.props.children, (newParentVNode._children = []), coerceToVNode, to true ,);
the let oldChildren = (oldParentVNode && oldParentVNode._children) || EMPTY_ARR;
// ...

// ⚛️ new traversal Children
for (I = 0; I <newChildren.length; I ++) {
childVNode = newChildren [I] = coerceToVNode (newChildren [I]); // normalized vnode
IF (childVNode == null) Continue
// ⚛️ find whether there oldChildren corresponding element, if it is found, removed from oldChildren as undefined by setting
// if not found then kept null
oldVNode = oldChildren [I];
for (J = 0; J <oldChildrenLength; J ++ ) {
oldVNode = oldChildren [J];
IF (oldVNode && && childVNode.type oldVNode.key childVNode.key == === oldVNode.type) {
oldChildren [J] = undefined;
BREAK;
}
oldVNode = null; // no find any old node, it represents a new
}
// ⚛️ recursive than the vnode
newDom = diff (parentDom, childVNode, oldVNode, mounts, ancestorComponent, null, oldDom);
// the vnode not uninstall diff
(newDom! = null) {IF
IF (childVNode._lastDomChild! = null) {
// Fragment ⚛️ this type is VNode
// Fragment or only have non-null component returns _lastDomChild Vnode Fragment from the end of the DOM Fragment trees began to compare:
// <A> <A>
// <> <>

Guess you like

Origin blog.51cto.com/14516164/2440085