preact是目前最小的react兼容庫了,所以學習它對提高anujs有很大的幫助。javascript
preact的一些模塊很是簡單。java
//vnode.js export function VNode() {}
一句話一個模塊,其實這個在preact-compat 會被擴展原型。node
//util.js //糅雜,至關於es6的Object.assign export function extend(obj, props) { for (let i in props) obj[i] = props[i]; return obj; } //用於異步執行一個函數,Promise比setTimeout的執行間隔過短 export const defer = typeof Promise=='function' ? Promise.resolve().then.bind(Promise.resolve()) : setTimeout;
有關異步的內容能夠看個人書《javascript框架設計》,這裏有詳細介紹。這其實也涉及到microtask, macrotask的概念,有興趣的人能夠搜索一下。react
preact的工具模塊是我見過的庫中最精簡的。es6
//options.js export default { // 用於同步刷新組件 //syncComponentUpdates: true, // 用於擴展VNode實例 //vnode(vnode) { } // 在組件插入DOM時調用,不一樣於componentDidMount,它是專門給框架或組件內部使用,好比說chrome debug tools這樣的工具進行擴展 // afterMount(component) { } // 同上,內置的後門 // afterUpdate(component) { } // 同上,內置的後門 // beforeUnmount(component) { } };
options這個模塊是用於擴展preact的功能,從而兼容官方react。chrome
// constants.js // 各類渲染模式 export const NO_RENDER = 0; //不渲染 export const SYNC_RENDER = 1;//React.render就是同步 export const FORCE_RENDER = 2;//forceUpdate export const ASYNC_RENDER = 3;//組件的更新是異步 export const ATTR_KEY = '__preactattr_';//在節點中添加的屬性 //用於識別那些樣式不用自動添加px的正則 export const IS_NON_DIMENSIONAL = /acit|ex(?:s|g|n|p|$)|rph|ows|mnc|ntw|ine[ch]|zoo|^ord/i;
下面是h.js,其實就是React.createElement,這裏作了一個不一樣於react的操做,就是當即將children扁平化,而且在扁平化過程成進行hydrate操做。hydrate是最先出現於inferno(另外一個著名的react-like框架),並相鄰的簡單數據類型合併成一個字符串。由於在react的虛擬DOM體系中,字符串至關於一個文本節點。減小children中的個數,就至關減小實際生成的文本節點的數量,也減小了之後diff的數量,能有效提升性能。數組
// h.js import { VNode } from './vnode'; import options from './options'; const stack = []; const EMPTY_CHILDREN = []; /** * nodeName至關於react的type * attributes至關於react的props * 這是preact早期設計不周,這個標新立異致使它在兼容官方react要走許多彎路 */ export function h(nodeName, attributes) { let children=EMPTY_CHILDREN, lastSimple, child, simple, i; for (i=arguments.length; i-- > 2; ) { stack.push(arguments[i]); } if (attributes && attributes.children!=null) { if (!stack.length) stack.push(attributes.children); delete attributes.children; } while (stack.length) { if ((child = stack.pop()) && child.pop!==undefined) { for (i=child.length; i--; ) stack.push(child[i]); } else { //減小比較類型 if (typeof child==='boolean') child = null; if ((simple = typeof nodeName!=='function')) { //轉化爲字符串 if (child==null) child = ''; //合併相鄰簡單類型 else if (typeof child==='number') child = String(child); else if (typeof child!=='string') simple = false; } if (simple && lastSimple) { children[children.length-1] += child; } else if (children===EMPTY_CHILDREN) { children = [child]; } else { children.push(child); } lastSimple = simple; } } let p = new VNode(); p.nodeName = nodeName; p.children = children; p.attributes = attributes==null ? undefined : attributes; p.key = attributes==null ? undefined : attributes.key; //對最終生成的虛擬DOM進行擴展 if (options.vnode!==undefined) options.vnode(p); return p; }
屬性 | react | preact |
---|---|---|
類別 | type | nodeName |
屬性包 | props | attributes |
孩子 | props.children | children |
數組追蹤用的trace by屬性 | key | key |
cloneElement與createElement是一對的,cloneElement是基於createElement實現框架
import { extend } from './util'; import { h } from './h'; export function cloneElement(vnode, props) { return h( vnode.nodeName, extend(extend({}, vnode.attributes), props), arguments.length>2 ? [].slice.call(arguments, 2) : vnode.children ); }
React.Component的實現dom
import { FORCE_RENDER } from './constants'; import { extend } from './util'; import { renderComponent } from './vdom/component'; import { enqueueRender } from './render-queue'; /** Base Component class. * Provides `setState()` and `forceUpdate()`, which trigger rendering. * @public * * @example * class MyFoo extends Component { * render(props, state) { * return <div />; * } * } */ export function Component(props, context) { //只有在_dirty爲true時才能更新組件 this._dirty = true; this.context = context; this.props = props; this.state = this.state || {}; } extend(Component.prototype, { /** * 當即對state進行合併,而官方react是將state先放到一個數組中 */ setState(state, callback) { let s = this.state; if (!this.prevState) this.prevState = extend({}, s); extend(s, typeof state==='function' ? state(s, this.props) : state); if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback); enqueueRender(this); }, //強制渲染,注意它與setState的實現是不同的 forceUpdate(callback) { if (callback) (this._renderCallbacks = (this._renderCallbacks || [])).push(callback); renderComponent(this, FORCE_RENDER); }, //將方法要求返回虛擬DOM或null render() {} });
Component依賴兩個方法enqueueRender與renderComponent,一個是異步的,一個是同步的。enqueueRender則是基於renderComponent上構建的。異步
咱們看render-queue.js,這模塊名與裏面的方法名對應不一致,算是一個瑕疵。
import options from './options'; import { defer } from './util'; import { renderComponent } from './vdom/component'; let items = []; //用於延遲渲染當前組件(setState) export function enqueueRender(component) { if (!component._dirty && (component._dirty = true) && items.push(component)==1) { (options.debounceRendering || defer)(rerender); } } export function rerender() { let p, list = items; items = []; while ( (p = list.pop()) ) { if (p._dirty) renderComponent(p); } }
到這裏,比較簡單的模塊已經介紹完了。render.js?這個模塊其實放到vdom文件夾比較合適。讀preact的源碼,其實能夠給咱們帶來許多啓迪,原來組件的渲染是有許多種模式的。這是一個要點。如何每次setState都是同步更新,這性能確定好差,而異步則要求怎麼更新纔是最適合。因而有了enqueueRender這樣的函數。下一節,咱們還會看到_disabled 這樣的開差,用來調濟更新的頻率。