美文网首页大前端从入门到跑路
Svelte笔记三:runtime源码解读

Svelte笔记三:runtime源码解读

作者: brandonxiang | 来源:发表于2020-08-08 16:37 被阅读0次

    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更多是服务于浏览器层面的数据流转化。

    相关文章

      网友评论

        本文标题:Svelte笔记三:runtime源码解读

        本文链接:https://www.haomeiwen.com/subject/tqdicktx.html