做为一名前端,咱们须要深刻学习react的运行机制,可是react源码量已经至关庞大,从学习的角度,性价比不高,因此学习一个react mini库是一个深刻学习react的一个不错的方法。html
preact是一个最小的react mini库,但因为其对尺寸的追求,它的不少代码可读性比较差,市面上也不多有全面且详细介绍的文章,本篇文章但愿能帮助你学习preact的源码。前端
在最开始我会先介绍preact总体流程,帮助您有一个总体概念,以便不会陷入源码的细枝末节里,而后会分别讲解preact各个值得学习的机制。建议与preact源码一块儿阅读本文。vue
但愿能帮你理清以下问题:node
如下图是preact源码大体流程图,如今看不懂不要紧,也不须要刻意记,在学习的过程当中,不妨根据此图试着猜测preact每一步都作了什么,下一步要作什么。react
在react的官方文档中,咱们能够得知,jsx内容在会被babel编译为如下格式:git
In const element = ( <h1 className="greeting"> Hello, world! </h1> ); Out const element = React.createElement( 'h1', {className: 'greeting'}, 'Hello, world!' );
这样经过createElement就能够生成虚拟dom树,在preact里面对应的函数是h。
h函数根据nodeName,attributes,children,返回一个虚拟dom树,这个虚拟dom树每每有三个属性:github
function h(nodeName, props, ...children){ .... // 其余代码 return { nodeName, props, // props中包含children key, // 为diff算法作准备 } }
这里不贴出preact的源代码,由于h函数的实现方式有不少,不但愿最开始的学习就陷入到细枝末节,只须要明白h函数的做用便可。算法
从上图中能够看到,preact主流程调用的第一个函数就是render,render函数很简单就是调用了一下diff函数。babel
function render(vnode, parent, merge) { return diff(merge, vnode, {}, false, parent, false); }
diff函数的主要做用是调用idiff函数,而后将idff函数返回的真实dom append到dom中app
function diff(dom, vnode, context, mountAll, parent, componentRoot) { // 返回的是一个真实的dom节点 let ret = idiff(dom, vnode, context, mountAll, componentRoot); // append the element if its a new parent if (parent && ret.parentNode !== parent) parent.appendChild(ret); }
接下来咱们要介绍idff函数,开启react高性能diff算法的大门,但在这以前,咱们应该了解react diff算法的前提:
基于第一个前提,不一样类型的节点就能够再也不向下比较,直接销毁,而后从新建立便可。
idiff函数主要分为三块,分别处理vnode三种状况:
对于string或Number:
// 若是要比较的dom是一个textNode,直接更改dom的nodeValue // 若是要比较的dom不是一个textNode,就建立textNode,而后回收老的节点树,回收的节点树会保留结构,而后保存在内存中,在// 须要的时候复用。(回收相关的处理会在以后详细说明) if (typeof vnode === 'string' || typeof vnode === 'number') { // update if it's already a Text node: if (dom && dom.splitText !== undefined && dom.parentNode && (!dom._component || componentRoot)) { /* istanbul ignore if */ /* Browser quirk that can't be covered: https://github.com/developit/preact/commit/fd4f21f5c45dfd75151bd27b4c217d8003aa5eb9 */ if (dom.nodeValue != vnode) { dom.nodeValue = vnode; } } else { // it wasn't a Text node: replace it with one and recycle the old Element out = document.createTextNode(vnode); if (dom) { if (dom.parentNode) dom.parentNode.replaceChild(out, dom); recollectNodeTree(dom, true); } } out.__preactattr_ = true; return out; }
若是nodeName是一个function,会直接调用buildComponentFromVNode方法
let vnodeName = vnode.nodeName; if (typeof vnodeName === 'function') { return buildComponentFromVNode(dom, vnode, context, mountAll); }
若是nodeName是一个字符串,如下很长的代码,就是作三步:
// Tracks entering and exiting SVG namespace when descending through the tree. isSvgMode = vnodeName === 'svg' ? true : vnodeName === 'foreignObject' ? false : isSvgMode; // If there's no existing element or it's the wrong type, create a new one: vnodeName = String(vnodeName); // 若是不存在dom对象,或者dom的nodeName和vnodeName不同的状况下 if (!dom || !isNamedNode(dom, vnodeName)) { out = createNode(vnodeName, isSvgMode); if (dom) { // 在后面你会发现preact的diffChildren的方式,是经过把真实dom的子节点与虚拟dom的子节点相比较,因此须要老的// 孩子暂时先移动到新的节点上 // move children into the replacement node while (dom.firstChild) { out.appendChild(dom.firstChild); } // if the previous Element was mounted into the DOM, replace it inline if (dom.parentNode) dom.parentNode.replaceChild(out, dom); // recycle the old element (skips non-Element node types) recollectNodeTree(dom, true); } } let fc = out.firstChild, props = out.__preactattr_, vchildren = vnode.children; // 把dom节点的attributes都放在了dom['__preactattr_']上 if (props == null) { props = out.__preactattr_ = {}; for (let a = out.attributes, i = a.length; i--;) { props[a[i].name] = a[i].value; } } // 若是vchildren只有一个节点,且是textnode节点时,直接更改nodeValue,优化性能 // Optimization: fast-path for elements containing a single TextNode: if (!hydrating && vchildren && vchildren.length === 1 && typeof vchildren[0] === 'string' && fc != null && fc.splitText !== undefined && fc.nextSibling == null) { if (fc.nodeValue != vchildren[0]) { fc.nodeValue = vchildren[0]; } } // 比较子节点,将真实dom的children与vhildren比较 // otherwise, if there are existing or new children, diff them: else if (vchildren && vchildren.length || fc != null) { innerDiffNode(out, vchildren, context, mountAll, hydrating || props.dangerouslySetInnerHTML != null); } diffAttributes(out, vnode.attributes, props); return out;