Svelte问世很久了,一直想写一篇好懂的原理分析文章,拖了这么久终于写了。
Demo1首先来看编译时,考虑如下App 组件代码: <h1>{count}</h1><script> let count = 0;</script> 这段代码经由编译器编译后产生如下代码,包括三部分: create_fragment 方法
count 的声明语句
class App 的声明语句
// 省略部分代码…function create_fragment(ctx) { let h1; return { c() { h1 = element("h1"); h1.textContent = `${count}`; }, m(target, anchor) { insert(target, h1, anchor); }, d(detaching) { if (detaching) detach(h1); } };} let count = 0; class App extends SvelteComponent { constructor(options) { super(); init(this, options, null, create_fragment, safe_not_equal, {}); }} export default App;
create_fragment首先来看create_fragment 方法,他是编译器根据App 的UI 编译而成,提供该组件与浏览器交互的方法,在上述编译结果中,包含3个方法: c ,代表create ,用于根据模版内容,创建对应DOM Element 。例子中创建H1 对应DOM Element :
h1 = element("h1");h1.textContent = `${count}`; m ,代表mount ,用于将c 创建的DOM Element 插入页面,完成组件首次渲染。例子中会将H1 插入页面:
insert(target, h1, anchor); insert 方法会调用target.insertBefore :
function insert(target, node, anchor) { target.insertBefore(node, anchor || null);} d ,代表detach ,用于将组件对应DOM Element 从页面中移除。例子中会移除H1 :
if (detaching) detach(h1); detach 方法会调用parentNode.removeChild :
function detach(node) { node.parentNode.removeChild(node);} 仔细观察流程图,会发现App 组件编译的产物没有图中fragment 内的p 方法。 这是因为App 没有变化状态的逻辑,所以相应方法不会出现在编译产物中。 可以发现,create_fragment 返回的c 、m 方法用于组件首次渲染。那么是谁调用这些方法呢?
SvelteComponent每个组件对应一个继承自SvelteComponent 的class ,实例化时会调用init 方法完成组件初始化,create_fragment 会在init 中调用: class App extends SvelteComponent { constructor(options) { super(); init(this, options, null, create_fragment, safe_not_equal, {}); }} 总结一下,流程图中虚线部分在Demo1 中的编译结果为: fragment :编译为create_fragment 方法的返回值
UI :create_fragment 返回值中m 方法的执行结果
ctx :代表组件的上下文,由于例子中只包含一个不会改变的状态count ,所以ctx 就是count 的声明语句
可以改变状态的Demo现在修改Demo ,增加update 方法,为H1 绑定点击事件,点击后count 改变: <h1 on:click="{update}">{count}</h1> <script> let count = 0; function update() { count++; }</script> 编译产物发生变化,ctx 的变化如下: // 从module顶层的声明语句let count = 0; // 变为instance方法function instance($$self, $$props, $$invalidate) { let count = 0; function update() { $$invalidate(0, count++, count); } return [count, update];} count 从module 顶层的声明语句变为instance 方法内的变量。之所以产生如此变化是因为App 可以实例化多个:
// 模版中定义3个App<App/><App/><App/>// 当count不可变时,页面渲染为:<h1>0</h1><h1>0</h1><h1>0</h1> 当count 不可变时,所有App 可以复用同一个count 。但是当count 可变时,根据不同App 被点击次数不同,页面可能渲染为: <h1>0</h1><h1>3</h1><h1>1</h1> 所以每个App 需要有独立的上下文保存count ,这就是instance 方法的意义。推广来说,Svelte 编译器会追踪<script> 内所有变量声明: - 是否包含改变该变量的语句,比如
count++ - 是否包含重新赋值的语句,比如
count = 1 - 等等情况
一旦发现,就会将该变量提取到instance 中,instance 执行后的返回值就是组件对应ctx 。 同时,如果执行如上操作的语句可以通过模版被引用,则该语句会被$$invalidate 包裹。 在Demo2 中,update 方法满足: |