最新公告
  • 欢迎您光临起源地模板网,本站秉承服务宗旨 履行“站长”责任,销售只是起点 服务永无止境!立即加入钻石VIP
  • 深入浅出svelte.js

    正文概述 掘金(yuxiaoliang)   2021-08-10   547

        最近有一个官网页,打算用svelte体验一下,顺便学习了一下svelte(发音:[svelt]),整体来说,svelte是比较简洁的,上手很快。不过与其说是一个前端框架,不如说是一个“dom操作编译器”。svelte的开发代码,在编译阶段会被编译成一系列的dom操作的代码,运行时的代码很少。因此svelte.js的体积很小(只保留了脏值检测更新和封装dom操作API等core代码)。本文从一下几个方面聊一聊对于svelte的认识。

    • svelte初体验
    • svelte的语法
    • Virtual Dom和Dom
    • 优缺点
    • svelte源码阅读

    原文地址在我的博客: github.com/fortheallli…


    一、svelte初体验

        我们直接来看官网的例子:

    深入浅出svelte.js

        实现的功能也很简单,就是两个Input的值求和,然后展示出来。用svelte编写的代码为:

    <script>
            let a = 1;
            let b = 2;
    </script>
    
    <input type="number" bind:value={a}>
    <input type="number" bind:value={b}>
    
    <p>{a} + {b} = {a + b}</p>
    

        上述代码很简洁,像vue一样也是类似style dom script的三段式写法,不过比vue更加简洁一点,比如dom不需要template包裹等等。

    同样的上述的例子的代码如果用react书写:

    import React, { useState } from 'react';
    
    export default () => { 
    
        const [a, setA] = useState(1); 
        const [b, setB] = useState(2); 
        function handleChangeA(event) { setA(+event.target.value); } 
        function handleChangeB(event) { setB(+event.target.value); }
    
        return ( 
    
           <div> 
              <input type="number" value={a} onChange={handleChangeA}/> 
              <input type="number" value={b} onChange={handleChangeB}/> 
              <p>{a} + {b} = {a + b}</p> 
           </div> 
    
        );
    
    }
    

        上述react的写法,必须要先弄懂useState的含义等,此外缺少了默认的双向数据绑定,代码有一点冗余。

        同样的上述的例子的代码如果用vue书写:

    <template> 
    
        <div> 
           <input type="number" v-model.number="a"> 
           <input type="number" v-model.number="b"> 
           <p>{{a}} + {{b}} = {{a + b}}</p> 
       </div> 
    
    </template> 
    
    <script> 
    
        export default { 
           data: function() { 
              return { a: 1, b: 2 }; 
           } 
        }; 
    
    </script>
    

    三者对比:

    框架名称sveltereactvue
    demo字符数145445263

        单纯的说,svelte编码只需要145个字符,比vue和react少,因此得出说svelte的编码体积更小,这样是不对的,因为svelte会在编译阶段将代码编译到更加贴近dom操作的代码,上述例子的代码,编译后的结果为:

    /* App.svelte generated by Svelte v3.38.3 */
    
        import {
                SvelteComponent,
                append,
                attr,
                detach,
                element,
                init,
                insert,
                listen,
                noop,
                run_all,
                safe_not_equal,
                set_data,
                set_input_value,
                space,
                text,
                to_number
    
        } from "svelte/internal";
    
    
    
        function create_fragment(ctx) {
                let input0;
                let t0;
                let input1;
                let t1;
                let p;
                let t2;
                let t3;
                let t4;
                let t5;
                let t6_value = /*a*/ ctx[0] + /*b*/ ctx[1] + "";
                let t6;
                let mounted;
                let dispose;
    
                return {
    
                        c() {
                                input0 = element("input");
                                t0 = space();
                                input1 = element("input");
                                t1 = space();
                                p = element("p");
                                t2 = text(/*a*/ ctx[0]);
                                t3 = text(" + ");
                                t4 = text(/*b*/ ctx[1]);
                                t5 = text(" = ");
                                t6 = text(t6_value);
                                attr(input0, "type", "number");
                                attr(input1, "type", "number");
                        },
    
                        m(target, anchor) {
                                insert(target, input0, anchor);
                                set_input_value(input0, /*a*/ ctx[0]);
                                insert(target, t0, anchor);
                                insert(target, input1, anchor);
                                set_input_value(input1, /*b*/ ctx[1]);
                                insert(target, t1, anchor);
                                insert(target, p, anchor);
                                append(p, t2);
                                append(p, t3);
                                append(p, t4);
                                append(p, t5);
                                append(p, t6);
    
                                if (!mounted) {
                                        dispose = [
                                                listen(input0, "input", /*input0_input_handler*/ ctx[2]),
                                                listen(input1, "input", /*input1_input_handler*/ ctx[3])
                                        ];
                                        mounted = true;
                                }
    
                        },
    
                        p(ctx, [dirty]) {
    
                                if (dirty & /*a*/ 1 && to_number(input0.value) !== /*a*/ ctx[0]) {
                                        set_input_value(input0, /*a*/ ctx[0]);
                                }
    
                                if (dirty & /*b*/ 2 && to_number(input1.value) !== /*b*/ ctx[1]) {
                                        set_input_value(input1, /*b*/ ctx[1]);
    
                                }
    
        
    
                                if (dirty & /*a*/ 1) set_data(t2, /*a*/ ctx[0]);
    
                                if (dirty & /*b*/ 2) set_data(t4, /*b*/ ctx[1]);
    
                                if (dirty & /*a, b*/ 3 && t6_value !== (t6_value = /*a*/ ctx[0] + /*b*/ ctx[1] + "")) set_data(t6, t6_value);
    
                        },
    
                        i: noop,
    
                        o: noop,
    
                        d(detaching) {
    
                                if (detaching) detach(input0);
    
                                if (detaching) detach(t0);
    
                                if (detaching) detach(input1);
    
                                if (detaching) detach(t1);
    
                                if (detaching) detach(p);
    
                                mounted = false;
    
                                run_all(dispose);
    
                        }
    
                };
    
        }
    
        
    
        function instance($$self, $$props, $$invalidate) {
    
                let a = 1;
    
                let b = 2;
    
                function input0_input_handler() {
    
                        a = to_number(this.value);
    
                        $$invalidate(0, a);
    
                }
    
                function input1_input_handler() {
    
                        b = to_number(this.value);
    
                        $$invalidate(1, b);
    
                }
                return [a, b, input0_input_handler, input1_input_handler];
    
        }
    
        
        class App extends SvelteComponent {
                constructor(options) {
                        super();
                        init(this, options, instance, create_fragment, safe_not_equal, {});
                }
    
        }
    
        
    
        export default App;
    

        在编译后生成的代码其实代码量也不小,是远远大于145个字符的,也不能说因为编译后的代码量大,所以说svelte有点名不副实,并不能减少运行时代码的体积。要考虑到svelte的运行时代码是很少的.我们来对比一下:

    框架名称reactvueangularsvelte
    体积42k22k89.5k1.6k

        从上述对比中可以看出,svelte的体积很少,虽然其业务代码在编译后会生产较多的代码。得益于较少的运行时代码。虽然svelte代码的随着业务的编写增量速度比较快,得益于其很小的包体积1.6k,对于一般中小型项目而言,整体运行的代码(编译后的代码+包体积)还是比较小的,所以可以说svelte项目的代码较小。不过对于大型项目而言,因为svelte随着业务的进行,运行时代码增量陡峭,大型项目体积并不会比react、vue等小,因此需要辩证看待。

        此外虽说svelte的代码在编译后体积很大,但是在编译前的代码,其实很简洁,这种简洁,一定程度上,可以增强开发体验。

    二、 svelte的语法

        svelte的写法跟vue有点类似,是指令式和响应式的。

    1. 基本用法

    <script>
        let name = 'world';
    </script>
    
    <h1>Hello {name}!</h1>
    <style>
    
      h1{
    
        color:red
    
      }
    </style>
    

    这是一个最简单的hello world的例子,上述代码中很简洁。在编译后的代码分为js编译和css编译。

    • js编译
    /* App.svelte generated by Svelte v3.38.3 */
    
    import {
    
            SvelteComponent,
    
            attr,
    
            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}!`;
    
                            attr(h1, "class", "svelte-khrn1o");
    
                    },
    
                    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;
    

    svelte/internal包中是一些封装了dom操作的函数。

    • css编译结果:

      h1.svelte-khrn1o{color:red}
      

    css是通过创建style标签引入到最后的dom中的。

    1. 指令形式和数据绑定

    <script>
    
        let a = 1;
    
        let b = 2;
    
        $: total =  a+b
    
    </script>
    
    
    
    <input type="number" bind:value={a}>
    
    <input type="number" bind:value={b}>
    
    <p>{a} + {b} = {total}</p>
    

    还是以上面的例子为例,上述就是一个指令形式+数据绑定的形式。跟vue的写法很相似,改例子绑定了input和a, input和b.效果如下: 深入浅出svelte.js

    这里的$total: 就是reactive statement. 类似vue中的计算属性。

    1. 组件compose

    //Name.svelte
    
    <script lang='typescript'>
    
        export let name = "yuxl"
    
    </script>
    
    
    
    <span>
    
        {name}
    
    </span>
    
    
    
    //Age.svelte
    
    <script lang='typescript'>
    
        export let age = 18
    
    </script>
    
    
    
    <span>
    
        {age}
    
    </span>
    
    
    
    //index.svelte
    
    
    
    <script>
    
    import Name from './Name.svelte'
    
    import Age from './Age.svelte' 
    
    </script>
    
    
    
    <div>
    
       <Name name="some name"/>
    
       <Age age = {20} />
    
    </div>
    

    在svelte中的组件的compose也是跟react中类似的,不同的是在react中export的属性就是组件的props,写法上比较简洁,此外,export const 和export function、export class这3个组件的props是只读的,不可写。

    1. 模版语法

        在svelte中,html相关的场景适用于模版语法,最简单的模版语法为:

    {#if answer === 42} <p>what was the question?</p> {/if}
    

        这里介绍几个在svelte中几个比较有趣的模版语法。

    • @debug
    <script>  
    
        export let name = "yuxl"
    
    </script>
    
     {@debug name}
    
      <span>
    
        {name}
    
      </span>
    

    运行debugger的结果为: 深入浅出svelte.js @debug 在后面跟的参数name发生变化的时候会进行debugger,从上图我们看到debugger的地方上下文的代码是编译后运行时,跟编码的时候有一点区别,也进一步说明,svelte可以看作是一个前端的编译框架,真正运行时的代码是编译后的结果。

    • @html
    <script lang='typescript'>
    
        export let name = "yuxl"
    
        const age = '<span>20</span>'
    
    </script>
    
    <div>
    
        <span>{name}</span>
    
        {@html age}
    
    </div>
    
    • #await 用法为:{#await expression}...{:then name}...{:catch name}...{/await}

      <script lang='typescript'>
      
        const promise = new Promise((resolve)=>{
      
          setTimeout(()=>{
      
              resolve("success")
      
          },2000)
      
        })
      </script>
      
      <div>
      
        {#await promise}
      
        <!-- promise is pending -->
      
        <p>waiting for the promise to resolve...</p>
      
        {:then value}
      
            <!-- promise was fulfilled -->
      
            <p>The value is {value}</p>
      
        {/await}
      
      </div>
      
    1. 动画效果

        在svelte中,对于原始的dom元素,自带了一些动画指令,在一般的官网或者活动页中,场景最多的就是动画效果,svelte自带的动画指令,因此在写官网的时候方便了不少。

    以transition:fly为例:

    <script>
    
        import { fly } from 'svelte/transition';
    
        let visible = true;
    
    </script>
    
    <label>
       <input type="checkbox" bind:checked={visible}>
        visible
    </label>
    
    
    
    {#if visible}
    
        <p transition:fly="{{ y: 200, duration: 2000 }}">
                Flies in and out
        </p>
    {/if}
    

    最后的结果为:

    深入浅出svelte.js

    当然在svelte中也支持自定义动画指令。

    1. 组件的生命周期

        svelte组件也提供了完整的生命周期。onMount、beforeUpdateafterUpdateonDestroy等。见名思意,这里不一一介绍,跟react & vue的组件生命周期近似。

    除了上述之外,svelte还支持自定义元素(custom element), store以及context等等

    三、Virtual Dom和Dom

        这个其实可以,比较客观的去看待,svelte的作者认为,Virtual Dom的性能并没有太大的问题,不管是diff算法还是render的过程都没有什么性能问题,不过作者认为,svelte不需要diff,还是有一点优势的。虽然diff很快,但是没有diff的话,显然会更快的得到渲染结果

        svelte的编译后的结果来看,所有的dom的变动都变为了直接的dom操作行为,是不需要做diff的,这种方法,没有diff/patch,因此从速度来看,肯定更快一些。 比如:

    <script>
    
    import { fade } from "svelte/transition";
    
    let visible = false
    
    function handleClick(){
    
        visible = true
    
    }
    
    </script>
    
    <div>
    
        <div on:click={handleClick}>点击</div>
    
        {#if visible}
    
            <div transition:fade ="{{ duration: 2000 }}" >
    
                fades in and out
    
            </div>
    
        {/if}
    
    </div>
    

    上述这个例子中,修改了visible,编译后的代码知道这个行为,这是一个确定的会如何影响dom的行为,编译后的结果部分为: 深入浅出svelte.js

        可以看到,state的改变如何影响dom在svelte的编译结果中都是很确定的。

        除了性能问题,svelte的作者认为,因为virtualDom的存在,需要保存new object和old object的虚拟dom对象,在react的编程中,每一次渲染都有这两个对象,这两个对象,在正常的开发中,很容易添加一些冗余代码:

    function MoreRealisticComponent(props) {
    
              const [selected, setSelected] = useState(null);
    
              return (
    
                <div>
    
                  <p>Selected {selected ? selected.name : 'nothing'}</p>
    
            
    
                  <ul>
    
                    {props.items.map(item =>
    
                      <li>
    
                        <button onClick={() => setSelected(item)}>
    
                          {item.name}
    
                        </button>
    
                      </li>
    
                    )}
    
                  </ul>
    
                </div>
    
              );
    
        }
    

        在这个例子中,为每一个li都绑定了一个事件,这是不过度优化情况下的正常下发,因为virtualDom虚拟dom的存在,每一次state更新的时候,每一个new object和old object都包含了每一个li的绑定函数,这些是冗余的代码,增加了代码的体积等。

    四、优缺点

    个人归纳了一下几个优缺点:

    • 优点:

      1. 体积很小,是真的小,包体积只有1.6k,对于小型项目比如官网首页,活动页等确实可以拿来试试。上手也很快,很轻量级。类似活动页这种简单页面的lowcoder系统,也可以尝试一下,因为框架本身提供的api,应该是目前前端框架里面最简单的。
      2. no virtual dom的形式一定程度上确实要快一些,没有了diff/path
    • 缺点

      1. 虽然包的体积小,但是编译后的代码其实并不小,代码总量的增加曲线其实还是有一定陡峭的。在大型项目中没有证明自己。
      2. 生态问题,生态其实并不是很完善,虽然类似的比如组件库之类的都有,但是没有很完善。

      五、源码阅读

        首先svelte的源码分为两部分,compiler和runtime,compiler主要的作用是将开发代码编译成运行时的代码,具体如何编译不是本文所要关注的代码。本文主要关注的是编译后的运行时的代码runtime。

    1. dom操作相关core api

    我们以最简单的hello world为例: svelte编译前源码:

    <h1>Hello world!</h1>
    

    svelte编译后的代码

    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 world!";
    
                    },
    
                    m(target, anchor) {
    
                            insert(target, h1, anchor);
    
                    },
    
                    p: noop,
    
                    i: noop,
    
                    o: noop,
    
                    d(detaching) {
    
                            if (detaching) detach(h1);
    
                    }
    
            };
    
    }
    
    
    
    class App extends SvelteComponent {
    
            constructor(options) {
    
                    super();
    
                    init(this, options, null, create_fragment, safe_not_equal, {});
    
            }
    
    }
    
    
    
    export default App;
    

    这里的App就可以直接使用了,比如渲染到一个父dom中可以这样使用:

    import App from './App.svelte'
    
    
    
    var app = new App({
    
      target: document.body,
    
    });
    
    export default app;
    

    上述方法就可以把App这个编译后的运行时组件渲染到body中,我们来看编译后的代码。

    • create_fragment

    在svelte组件中,与dom相关的部分封装在了create_fragment中,该函数创建了一个Fragment, 该函数返回一个包含dom操作的对象:

    export 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;
    
    }
    

    在上述的例子中,c对应创建一个子dom元素,m表示创建元素要渲染元素时需要执行的函数,d表示删除元素时的操作。上述的例子中:

    function create_fragment(ctx) {
    
            let h1;
    
    
    
            return {
    
                    c() {
    
                            h1 = element("h1");
    
                            h1.textContent = "Hello world!";
    
                    },
    
                    m(target, anchor) {
    
                            insert(target, h1, anchor);
    
                    },
    
                    p: noop,
    
                    i: noop,
    
                    o: noop,
    
                    d(detaching) {
    
                            if (detaching) detach(h1);
    
                    }
    
            };
    
    }
    

    在m中的intert和d中的detach方法,都是原生的dom操作方法,上述Fragment的意思是创建了h1这个dom,并在渲染的时候插入到目标dom节点中,在Fragment这个组件元素被销毁的时候,销毁被创建的子dom元素 h1。

    element、insert、detach等方法都是原生的dom操作,具体源码如下所示:

    export function element<K extends keyof HTMLElementTagNameMap>(name: K) {
    
            return document.createElement<K>(name);
    
    }
    
    
    
    export function insert(target: NodeEx, node: NodeEx, anchor?: NodeEx) {
    
            target.insertBefore(node, anchor || null);
    
    }
    
    export function detach(node: Node) {
    
            node.parentNode.removeChild(node);
    
    }
    
    • SvelteComponent
    export class SvelteComponent {
    
            $$: T$$;
    
            $$set?: ($$props: any) => void;
    
    
    
            $destroy() {
    
                    destroy_component(this, 1);
    
                    this.$destroy = noop;
    
            }
    
    
    
            $on(type, callback) {
    
                    const callbacks = (this.$$.callbacks[type] || (this.$$.callbacks[type] = []));
    
                    callbacks.push(callback);
    
    
    
                    return () => {
    
                            const index = callbacks.indexOf(callback);
    
                            if (index !== -1) callbacks.splice(index, 1);
    
                    };
    
            }
    
    
    
            $set($$props) {
    
                    if (this.$$set && !is_empty($$props)) {
    
                            this.$$.skip_bound = true;
    
                            this.$$set($$props);
    
                            this.$$.skip_bound = false;
    
                    }
    
            }
    
    }
    

    SvelteComponent组件定义了如何销毁组件以及如何设置组件的属性,以及如何增加监听函数,其中最重要的是定义了组件的实例属性 .

    interface T$$ {
    
            dirty: number[];
    
            ctx: null|any;
    
            bound: any;
    
            update: () => void;
    
            callbacks: any;
    
            after_update: any[];
    
            props: Record<string, 0 | string>;
    
            fragment: null|false|Fragment;
    
            not_equal: any;
    
            before_update: any[];
    
            context: Map<any, any>;
    
            on_mount: any[];
    
            on_destroy: any[];
    
            skip_bound: boolean;
    
            on_disconnect: any[];
    
    }
    

    发现SvelteComponent组件确实包含了ctx上下文内容,以及组件的生命周期属性,以及组件的脏值检测等相关的属性。

    • init函数 `js export function init(component, options, instance, create_fragment, not_equal, props, dirty = [-1]) {

      const parent_component = current_component;
      
        set_current_component(component);
      
      
      
        const $$: T$$ = component.$$ = {
      
                fragment: null,
      
                ctx: null,
      
      
      
                // state
      
                props,
      
                update: noop,
      
                not_equal,
      
                bound: blank_object(),
      
      
      
                // lifecycle
      
                on_mount: [],
      
                on_destroy: [],
      
                on_disconnect: [],
      
                before_update: [],
      
                after_update: [],
      
                context: new Map(parent_component ? parent_component.$$.context : options.context || []),
      
      
      
                // everything else
      
                callbacks: blank_object(),
      
                dirty,
      
                skip_bound: false
      
        };
      
      
      
        let ready = false;
      
      
      
        $$.ctx = instance
      
                ? instance(component, options.props || {}, (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;
      
                })
      
                : [];
      
      
      
        $$.update();
      
        ready = true;
      
        run_all($$.before_update);
      
      
      
        // `false` as a special case of no DOM component
      
        $$.fragment = create_fragment ? create_fragment($$.ctx) : false;
      
      
      
        if (options.target) {
      
            flush();
      
        }
      
      
      
        set_current_component(parent_component);
      }
      

      init函数在SvelteComponent组件内部调用,用于实例属性的初始化。这里最重要的是$$.ctx的赋值部分,后续会用来做脏值检测。ctx中保存了所有的再多次渲染中都存在的值,包含了内部的state以及监听处理函数等等。

      1. 脏值检测和更新部分

    这里我们以一个带有鼠标时间的svelte组件为例,

    编译前的代码:

    <script>
    
            let m = { x: 0, y: 0 };
    
            function handleMousemove(event) {
    
                    m.x = event.clientX;
    
                    m.y = event.clientY;
    
            }
    
    </script>
    
    <div on:mousemove={handleMousemove}>
    
            The mouse position is {m.x} x {m.y}
    
    </div>
    

    svelte编译后的代码与hello world相比增加的代码:

    function create_fragment(ctx) {
    
            let div;
    
            let t0;
    
            let t1_value = /*m*/ ctx[0].x + "";
    
            let t1;
    
            let t2;
    
            let t3_value = /*m*/ ctx[0].y + "";
    
            let t3;
    
            return {
    
                   ...
    
                    p(ctx, [dirty]) {
    
                            if (dirty & /*m*/ 1 && t1_value !== (t1_value = /*m*/ ctx[0].x + "")) set_data(t1, t1_value);
    
                            if (dirty & /*m*/ 1 && t3_value !== (t3_value = /*m*/ ctx[0].y + "")) set_data(t3, t3_value);
    
                    },
    
                   ...
    
            };
    
    }
    
    
    
    function instance($$self, $$props, $$invalidate) {
    
            let m = { x: 0, y: 0 };
    
            function handleMousemove(event) {
    
                    $$invalidate(0, m.x = event.clientX, m);
    
                    $$invalidate(0, m.y = event.clientY, m);
    
            }
    
            return [m, handleMousemove];
    
    }
    

    这里多了一个instance函数,而这个instance函数在svelteComponent的init函数中就是用作脏值检测和更新的。

    $$.ctx = instance
    
        ? instance(component, options.props || {}, (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;
    
        })
    
        : [];
    

    如果值发生了变动,就触发make_dirty函数:

    function make_dirty(component, i) {
    
            if (component.$$.dirty[0] === -1) {
    
                    dirty_components.push(component);
    
                    schedule_update();
    
                    component.$$.dirty.fill(0);
    
            }
    
            component.$$.dirty[(i / 31) | 0] |= (1 << (i % 31));
    
    }
    

    make_dirty标记了哪一些脏组件,然后对脏组件执行schedule_update方法来更新组件:

    export function schedule_update() {
    
            if (!update_scheduled) {
    
                    update_scheduled = true;
    
                    resolved_promise.then(flush);
    
            }
    
    }
    

    schedule_update在需要更新时候,在下一个微任务重执行flush:

    export function flush() {
    
            if (flushing) return;
    
            flushing = true;
    
            do {
    
                    // first, call beforeUpdate functions
    
                    // and update components
    
                    for (let i = 0; i < dirty_components.length; i += 1) {
    
                            const component = dirty_components[i];
    
                            set_current_component(component);
    
                            update(component.$$);
    
                    }
    
                    set_current_component(null);
    
                    ...
    
                    render_callbacks.length = 0;
    
            } while (dirty_components.length);
    
    }
    

    简化后的flush方法如上所示,就是遍历整个脏组件,执行所有的脏组件中的更新方法update.update方法的定义为:

    function update($$) {
    
            if ($$.fragment !== null) {
    
                    $$.update();
    
                    run_all($$.before_update);
    
                    const dirty = $$.dirty;
    
                    $$.dirty = [-1];
    
                    $$.fragment && $$.fragment.p($$.ctx, dirty);
    
    
    
                    $$.after_update.forEach(add_render_callback);
    
            }
    
    }
    

    update方法标记自身组件为脏,并且制定自身组件fragment中的p(全名:update)也就是前面的fragment中的:

    p(ctx, [dirty]) {
    
                            if (dirty & /*m*/ 1 && t1_value !== (t1_value = /*m*/ ctx[0].x + "")) set_data(t1, t1_value);
    
                            if (dirty & /*m*/ 1 && t3_value !== (t3_value = /*m*/ ctx[0].y + "")) set_data(t3, t3_value);
    
                    },
    

    在p方法中,直接操作dom改变UI。

    总结来看,组件更新的步骤为以下几步:

    1. 事件或者其他操作出发更新流程
    2. 在instance的$$invalidate方法中,比较操作前后ctx中的值有没有发生改变,如果发生改变则继续往下
    3. 执行make_dirty函数标记为脏值,添加带有脏值需要更新的组件,从而继续触发更新
    4. 执行schedule_update函数
    5. 执行flush函数,将所有的脏值组件取出,以此执行其update方法
    6. 在update方法中,执行的是Fragment自身的p方法,p方法做的事情就是确定需要更新组件,并操作和更新dom组件,从而完成了最后的流程

    起源地下载网 » 深入浅出svelte.js

    常见问题FAQ

    免费下载或者VIP会员专享资源能否直接商用?
    本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
    提示下载完但解压或打开不了?
    最常见的情况是下载不完整: 可对比下载完压缩包的与网盘上的容量,若小于网盘提示的容量则是这个原因。这是浏览器下载的bug,建议用百度网盘软件或迅雷下载。若排除这种情况,可在对应资源底部留言,或 联络我们.。
    找不到素材资源介绍文章里的示例图片?
    对于PPT,KEY,Mockups,APP,网页模版等类型的素材,文章内用于介绍的图片通常并不包含在对应可供下载素材包内。这些相关商业图片需另外购买,且本站不负责(也没有办法)找到出处。 同样地一些字体文件也是这种情况,但部分素材会在素材包内有一份字体下载链接清单。
    模板不会安装或需要功能定制以及二次开发?
    请QQ联系我们

    发表评论

    还没有评论,快来抢沙发吧!

    如需帝国cms功能定制以及二次开发请联系我们

    联系作者

    请选择支付方式

    ×
    迅虎支付宝
    迅虎微信
    支付宝当面付
    余额支付
    ×
    微信扫码支付 0 元