大前端从入门到跑路

Svelte笔记三:runtime源码解读

2020-08-08  本文已影响0人  brandonxiang

svelte的源码很简单是由两大部分组成,compiler和runtime。

compiler就是一个编译器将svelte模版语法转换为浏览器能够识别的代码。而runtime则是在浏览器中帮助业务代码运行的运行时函数。所以说runtime是svelte框架最核心的部分,它也解释了svelte是如何在没有virtual dom而运行的。今天我们review一下runtime代码。

Fragment

Svelte官方example提供了compile出来的Js output。这些output就是运行在浏览器的源码,根据内容知道svelte的基本运作。下面这个栗子很简单,就是对hello的一个字符串插值。而name是一个变量。

<script>
    let name = 'world';
</script>

<h1>Hello {name}!</h1>

编译出来的结果:

/* App.svelte generated by Svelte v3.24.0 */
import {
    SvelteComponent,
    detach,
    element,
    init,
    insert,
    noop,
    safe_not_equal
} from "svelte/internal";

function create_fragment(ctx) {
    let h1;

    return {
        c() {
            h1 = element("h1");
            h1.textContent = `Hello ${name}!`;
        },
        m(target, anchor) {
            insert(target, h1, anchor);
        },
        p: noop,
        i: noop,
        o: noop,
        d(detaching) {
            if (detaching) detach(h1);
        }
    };
}

let name = "world";

class App extends SvelteComponent {
    constructor(options) {
        super();
        init(this, options, null, create_fragment, safe_not_equal, {});
    }
}

export default App;

编译出来的结果就是有一个初始化函数,叫create_fragment,它是用于初始dom的挂载。它使用了element函数,通过查阅源码src/runtime/internal/dom,我们知道它的作用就是用来创建h1标签实例,并且填入可变内容。除了element之外,还有spacetextsvg_element等是在js端生成真实dom的处理。

export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
    return document.createElement<K>(name);
}

export function text(data: string) {
    return document.createTextNode(data);
}

export function space() {
    return text(' ');
}

create_fragment的过程还包含有c,m,p,i,o,d等特殊名称的函数,这些函数并非编译混淆,而是Fragment内部的生命周期缩写。Fragment指得是真实dom的节点,它拥有着独立的生命周期和属性。源码中src/runtime/internal/Component介绍了它的定义,它是一个真实的dom元素集合,它的属性并非组件属性(如下方ts类型定义),分别包含了create,claim,hydrate,mount,update,mesure,fix,animate,intro,outro,destory,组件的真实变化会影响Fragment的变化,Fragment的变化影响真实的dom,从上面例子看在create的过程中它创建了h1标签,在mount的过程将刚才创建的h1挂载到页面中,在update的过程没有任何操作,在detach的过程销毁该Fragment。

interface Fragment {
    key: string|null;
    first: null;
    /* create  */ c: () => void;
    /* claim   */ l: (nodes: any) => void;
    /* hydrate */ h: () => void;
    /* mount   */ m: (target: HTMLElement, anchor: any) => void;
    /* update  */ p: (ctx: any, dirty: any) => void;
    /* measure */ r: () => void;
    /* fix     */ f: () => void;
    /* animate */ a: () => void;
    /* intro   */ i: (local: any) => void;
    /* outro   */ o: (local: any) => void;
    /* destroy */ d: (detaching: 0|1) => void;
}

Component

SvelteComponent则是包含了svelte组件内置的属性和生命周期,它们与Fragment的属性和生命周期是息息相关,SvelteComponent是依赖于Fragment,组件的变化会触发Fragment的变化。它是一个相辅相成的组合。源码中还有SvelteComponent和SvelteElement的细分,不同点在于Web Component的组件的支持,这里就不再展开。

Component拥有四个生命周期,分别是mount,beforeUpdate, afterUpdate,destory。没有create阶段是因为svelte没有virtual dom。所以在组件层面,它没有像vue那么复杂。

数据流

react的单向数据流,vue的双向绑定,那么svelte是怎么样实现数据流的呢?

下面是我们业务中经常见到的代码,点击按钮请求数据然后设置到变量,触发dom内容的变化。svelte的写法形似vue的写法,但是它的runtime原理并没有双向绑定。编译后的代码除了有上面所说的create和mount等fragment生命周期属性外,其他代码更多表现了数据流的形式。

<script>
    let num = 1;

    async function handleClick() {
        const res = await fetch(`tutorial/random-number`);
        const text = await res.text();

        if (res.ok) {
            num = text;
            return text;
        } else {
            throw new Error(text);
        }
    }
</script>

<button on:click={handleClick}>
    generate random number
</button>


<p>The number is {num}</p>

编译出来的结果:

/* App.svelte generated by Svelte v3.24.0 */
import {
    SvelteComponent,
    append,
    detach,
    element,
    init,
    insert,
    listen,
    noop,
    safe_not_equal,
    set_data,
    space,
    text
} from "svelte/internal";

function create_fragment(ctx) {
    let button;
    let t1;
    let p;
    let t2;
    let t3;
    let mounted;
    let dispose;

    return {
        c() {
            button = element("button");
            button.textContent = "generate random number";
            t1 = space();
            p = element("p");
            t2 = text("The number is ");
            t3 = text(/*num*/ ctx[0]);
        },
        m(target, anchor) {
            insert(target, button, anchor);
            insert(target, t1, anchor);
            insert(target, p, anchor);
            append(p, t2);
            append(p, t3);

            if (!mounted) {
                dispose = listen(button, "click", /*handleClick*/ ctx[1]);
                mounted = true;
            }
        },
        p(ctx, [dirty]) {
            if (dirty & /*num*/ 1) set_data(t3, /*num*/ ctx[0]);
        },
        i: noop,
        o: noop,
        d(detaching) {
            if (detaching) detach(button);
            if (detaching) detach(t1);
            if (detaching) detach(p);
            mounted = false;
            dispose();
        }
    };
}

function instance($$self, $$props, $$invalidate) {
    let num = 1;

    async function handleClick() {
        const res = await fetch(`tutorial/random-number`);
        const text = await res.text();

        if (res.ok) {
            $$invalidate(0, num = text);
            return text;
        } else {
            throw new Error(text);
        }
    }

    return [num, handleClick];
}

class App extends SvelteComponent {
    constructor(options) {
        super();
        init(this, options, instance, create_fragment, safe_not_equal, {});
    }
}

export default App;

代码中,handleClick函数被封装在一个名为instance的方法当中,而它的入参当中有个$$invalidate的回调函数,用于变量的设置,把接口异步获取的数据设置回调函数当中。而它在组件的调用如下,重点在于回调函数当中,instance只会在初始化的时候调用,但是回调函数$$invalidate可以在各种异步情况调用。它会触发make_dirty的方法,而它触发了schedule_update,在一个微任务当中,触发flush将一段时间内的变量操作都执行掉。实现变量的处理,flush函数的具体实现请查看源码(src/runtime/internal/Component.ts)。flush的过程中会触发Fragment的update以及Component的update。

    $$.ctx = instance
        ? instance(component, prop_values, (i, ret, ...rest) => {
            const value = rest.length ? rest[0] : ret;
            if ($$.ctx && not_equal($$.ctx[i], $$.ctx[i] = value)) {
                if (!$$.skip_bound && $$.bound[i]) $$.bound[i](value);
                if (ready) make_dirty(component, i);
            }
            return ret;
        })
        : [];

由此可见,svelte更多是单向数据流,很多工作已经在compile的过程当中已经完成。runtime更多是服务于浏览器层面的数据流转化。

上一篇下一篇

猜你喜欢

热点阅读