大家好,我是Kason。Svelte已经存在很久了,一直想写一篇通俗易懂的原理分析文章。隔了这么久,终于写出来了。本文将围绕一张流程图和两个demo进行讲解。正确的吃法是用电脑打开这篇文章,按照流程图和demo边看边打字边学习。让我们开始吧。Demo1Svelte的实现原理如图所示:图中的Component为开发者编写的组件,内部虚线部分由Svelte编译器编译。图中的各个箭头是运行时的工作流。先看编译,考虑如下App组件代码:
{count}
完整代码见Demo1repl[1],浏览器会显示:This代码经过编译后,编译器生成如下代码,包括三部分:create_fragment方法count的声明语句App类的声明语句//部分代码省略...functioncreate_fragment(ctx){leth1;return{c(){h1=element("h1");h1.textContent=`${count}`;},m(target,anchor){insert(target,h1,anchor);},d(分离){if(detaching)detach(h1);}};}letcount=0;classAppextendsSvelteComponent{constructor(options){super();init(this,options,null,create_fragment,safe_not_equal,{});}}exportdefaultApp;create_fragment首先看create_fragment方法,它是编译器的UI根据App进行编译,为组件提供与浏览器交互的方法。在上面的编译结果中,有3个方法:c代表create,用于根据模板内容创建对应的DOMElement。在示例中,创建与DOM元素对应的H1:h1=element("h1");h1.textContent=`${count}`;m代表mount,用于将c创建的DOMElement插入到页面中,完成组件的第一次渲染。在示例中,H1将被插入到页面中:insert(target,h1,anchor);insert方法会调用target.insertBefore:functioninsert(target,node,anchor){target.insertBefore(node,anchor||null);}d,代表detach,用于从页面中移除组件对应的DOMElement。在示例中,H1将被删除:if(detaching)detach(h1);detach方法会调用parentNode.removeChild:functiondetach(node){node.parentNode.removeChild(node);}仔细观察流程图,会发现App组件编译出来的fragment中并没有p方法在图中。这是因为App没有“改变状态”的逻辑,所以编译后的产品中不会出现相应的方法。可以发现create_fragment返回的c和m方法用于组件的第一次渲染。那么谁调用这些方法呢?SvelteComponent的每个组件都对应一个继承自SvelteComponent的类。实例化时会调用init方法完成组件初始化,init中会调用create_fragment:}}综上,Demo1中流程图中虚线的编译结果为:组件的上下文。由于例子中只包含一个不会改变的状态count,ctx就是demo中可以改变状态的count的statement语句。现在修改demo,添加update方法,将点击事件绑定到H1。点击后计数变化:
{count}完整代码见Demo2repl[2]product发生变化,ctx的变化如下://从模块顶层的声明语句letcount=0;//进入实例方法functioninstance($$self,$$props,$$invalidate){letcount=0;functionupdate(){$$invalidate(0,count++,count);}return[count,update];}count从模块顶层的声明语句变为实例方法中的变量。之所以这样改,是因为可以实例化多个App://模板中定义了三个App//计数不可变时,页面渲染为:0
0
0
当计数不可变时,所有应用都可以重复使用相同的计数。但是当count是可变的时候,根据不同app的点击次数,页面可能会呈现为:0
3
1
所以每一个app需要有独立的上下文来保存计数,这就是实例方法的意思。一般来说,Svelte编译器会跟踪