preact源碼學習(1)

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 這樣的開差,用來調濟更新的頻率。

相關文章
相關標籤/搜索