preact源码解读(1)

前言

  • 和上次说的一样这次带来preact的解读

  • preact实际上把它当作是一个精简版react就好了。

  • 这次我抄下了preact,并且改写了代码, 命名为zreact

  • 把之前将事件,props之类的单独放出来,这样这份zreact

  • 可以支持ie8,虽然并没有什么用。

  • 这次代码解读顺序按使用preact的代码顺序。

  • 这里是第一篇,createElement,也就是vue,react的render所返回的VNode对象。

  • 平常则是使用babel+jsx来生成createElement调用。

  • vue常用则是template,但是通过webpack会做到预先转换为render。

一、jsx的转换原理。

对于preact来说,最常见的就是jsx。
下面是一个最简单的使用preact。

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实例。

二、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

三、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

四、后记

  • 这次的blog感觉好短,我已经没有东西写了。

  • 话说回来vue的template,现在看来不如说是一个变异的jsx语法。

  • 感觉明明是在读preact源码却对vue的实现更加的理解了。

  • 下一篇应该是Component了。

五、资料

  1. preact源码

  2. zreact源码

  3. React 初窥:JSX 详解

  4. 原文地址

    原文作者:zeromake
    原文地址: https://segmentfault.com/a/1190000010337622
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞