Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

深入浅出svelte.js #71

Open
forthealllight opened this issue Jul 6, 2021 · 0 comments
Open

深入浅出svelte.js #71

forthealllight opened this issue Jul 6, 2021 · 0 comments

Comments

@forthealllight
Copy link
Owner

forthealllight commented Jul 6, 2021

深入浅出svelte.js

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

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

一、svelte初体验

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

a1

    实现的功能也很简单,就是两个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>

三者对比:

框架名称 svelte react vue
demo字符数 145 445 263

    单纯的说,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的运行时代码是很少的.我们来对比一下:

框架名称 react vue angular svelte
体积 42k 22k 89.5k 1.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.效果如下:
a2

这里的$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中几个比较有趣的模版语法。

<script>  

    export let name = "yuxl"

</script>

 {@debug name}

  <span>

    {name}

  </span>

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

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

最后的结果为:

a4

当然在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的行为,编译后的结果部分为:
a5

    可以看到,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组件,从而完成了最后的流程
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant