preact源碼解讀(1)

前言

  • 和上次說的同樣此次帶來preact的解讀javascript

  • preact實際上把它看成是一個精簡版react就行了。html

  • 此次我抄下了preact,而且改寫了代碼, 命名爲zreactvue

  • 把以前將事件,props之類的單獨放出來,這樣這份zreactjava

  • 能夠支持ie8,雖然並無什麼用。node

  • 此次代碼解讀順序按使用preact的代碼順序。react

  • 這裏是第一篇,createElement,也就是vue,react的render所返回的VNode對象。webpack

  • 日常則是使用babel+jsx來生成createElement調用。git

  • vue經常使用則是template,可是經過webpack會作到預先轉換爲render。github

1、jsx的轉換原理。

對於preact來講,最多見的就是jsx。
下面是一個最簡單的使用preact。web

import {h, render, Component} from "preact";

/** @jsx h */
// 經過上面的註釋告訴babel使用什麼方法做爲VNode的建立函數。
// 若是不使用這個默認會是React.createElement,
// 或者經過下面的babel配置來修改
class App extends Component {
    render(props) {
        return <h1>App</h1>;
    }
}
var test = "";
render(
    <div className="test">
        <span style={test}>測試</span>
        <App></App>
    </div>
, document.body)

.babelrc

{
    "presets": ["es2015"],
    "plugins": [
        ["transform-react-jsx", { "pragma":"h" }]
    ]
}

經過babel轉換後會變成

import {h, render} from "preact";

class App extends Component {
    render() {
        return h("h1", null, "App");
    }
}
var test = "";
render(
    h(
        "div",
        { className: "test" },
        h("span", { style: test }, "測試"),
        h(App)
    ),
    document.body
)

因此對於preact最早執行的東西是這個h函數也就是createElement
對於jsx標準的createElement函數簽名爲

interface IKeyValue {
    [name: string]: any;
}
/**
* 標準JSX轉換函數
* @param {string|Component} nodeName 組件
* @param {IKeyValue} attributes 組件屬性
* @param {any[]} childs 這個VNode的子組件
*/
function h(
    nodeName: string | function,
    attributes: IKeyValue,
    ...childs: any[]
): VNode;
class VNode {
    public nodeName: string| Component;
    public children: any[];
    public attributes: IKeyValue | undefined;
    public key: any | undefined;
}

因此這裏的標準jsx很是簡單。

  1. 第一個參數爲原生html組件或者Component類。

  2. 第二個參數爲該組件的屬性,及自定義屬性。

  3. 第三個參數及後面的全部都是這個組件的子組件。

  4. 其中第三個及後面的參數爲數組就會被分解放入子組件中。

  5. 最後返回一個VNode實例。

2、createElement的實現

function h(nodeName: string | Component, attributes: IKeyValue, ...args: any[]) {
    // 初始化子元素列表
    const stack: any[] = [];
    const children: any[] = [];
    // let i: number;
    // let child: any;
    // 是否爲原生組件
    let simple: boolean;
    // 上一個子元素是否爲原生組件
    let lastSimple: boolean = false;
    // 把剩餘的函數參數所有倒序放入stack
    for (let i = args.length; i--; ) {
        stack.push(args[i]);
    }
    // 把元素上屬性的children放入棧
    if (attributes && attributes.children != null) {
        if (!stack.length) {
            stack.push(attributes.children);
        }
        // 刪除
        delete attributes.children;
    }
    // 把stack一次一次取出
    while (stack.length) {
        // 取出最後一個
        let child: any = stack.pop();
        if (child && child.pop !== undefined) {
            // 若是是個數組就倒序放入stack
            for (let i = child.length; i-- ; ) {
                stack.push(child[i]);
            }
        } else {
            // 清空布爾
            if (typeof child === "boolean") {
                child = null;
            }
            // 判斷當前組件是否爲自定義組件
            simple = typeof nodeName !== "function";
            if (simple) {
                // 原生組件的子元素處理
                if (child == null) {
                    // null to ""
                    child = "";
                } else if (typeof child === "number") {
                    // num to string
                    child = String(child);
                } else if (typeof child !== "string") {
                    // 不是 null,number,string 的不作處理
                    // 而且設置標記不是一個字符串
                    simple = false;
                }
            }
            if (simple && lastSimple) {
                // 當前爲原生組件且子元素爲字符串,而且上一個也是。
                // 就把當前子元素加到上一次的後面。
                children[children.length - 1] += child;
            } else {
                // 其它狀況直接加入children
                children.push(child);
            }
            /* else if (children === EMPTY_CHILDREN) {
                children = [child];
            } */
            // 記錄此次的子元素狀態
            lastSimple = simple;
        }
    }
    const p = new VNode();
    // 設置原生組件名字或自定義組件class(function)
    p.nodeName = nodeName;
    // 設置子元素
    p.children = children;
    // 設置屬性
    p.attributes = attributes == null ? undefined : attributes;
    // 設置key
    p.key = attributes == null ? undefined : attributes.key;
    // vnode 鉤子
    if (options.vnode !== undefined) {
        options.vnode(p);
    }
    return p;
}

這個標準jsx的VNode生成函數很簡單,這邊要注意的是子組件是連續的字符串。
會被合併成一個,這樣能夠防止在生成dom時,建立多餘的Text

3、clone-element

import { h } from "./h";
import { VNode } from "./vnode";
import { extend } from "./util";

/**
 * 經過VNode對象新建一個自定義的props,children的VNode對象
 * @param vnode 舊vnode
 * @param props 新的props
 * @param children 新的子組件
 */
export function cloneElement(vnode: VNode, props: any, ...children: any[]) {
    const child: any = children.length > 0 ? children : vnode.children;
    return h(
        vnode.nodeName,
        extend({}, vnode.attributes, props),
        child,
    );
}

clone-element依賴於createElement

4、後記

  • 此次的blog感受好短,我已經沒有東西寫了。

  • 話說回來vue的template,如今看來不如說是一個變異的jsx語法。

  • 感受明明是在讀preact源碼卻對vue的實現更加的理解了。

  • 下一篇應該是Component了。

5、資料

  1. preact源碼

  2. zreact源碼

  3. React 初窺:JSX 詳解

  4. 原文地址

相關文章
相關標籤/搜索