JavaScript开发简单易懂的Svelte实现原理详解 目录 Demo1 create_fragment SvelteComponent 可以改变状态的Demo Svelte问世很久了,一直想写一篇好懂的原理分析文章,拖了这么久终于写了. Demo1 首先来看编译时,考虑如下App组件代码: h1{count}/h1 script let count = 0; /scri
目录
- Demo1
- create_fragment
- SvelteComponent
- 可以改变状态的Demo
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方法满足:
- 包含改变
count的语句 ——count++ - 可以通过模版被引用 —— 作为点击回调函数
所以编译后的update内改变count的语句被$$invalidate方法包裹:
// 源代码中的update
function update() {
count++;
}
// 编译后instance中的update
function update() {
$$invalidate(0, count++, count);
}
- 更新
ctx中保存状态的值,比如Demo2中count++ - 标记
dirty,即标记App UI中所有和count相关的部分将会发生变化 - 调度更新,在
microtask中调度本次更新,所有在同一个macrotask中执行的$$invalidate都会在该macrotask执行完成后被统一执行,最终会执行组件fragment中的p方法
p方法是Demo2中新的编译产物,除了p之外,create_fragment已有的方法也产生相应变化:
c() {
h1 = element("h1");
// count的值变为从ctx中获取
t = text(/*count*/ ctx[0]);
},
m(target, anchor) {
insert(target, h1, anchor);
append(h1, t);
// 事件绑定
dispose = listen(h1, "click", /*update*/ ctx[1]);
},
p(ctx, [dirty]) {
// set_data会更新t保存的文本节点
if (dirty & /*count*/ 1) set_data(t, /*count*/ ctx[0]);
},
d(detaching) {
if (detaching) detach(h1);
// 事件解绑
dispose();
}
p方法会执行$$invalidate中标记为dirty的项对应的更新函数。
在Demo2中,App UI中只引用了状态count,所以update方法中只有一个if语句,如果UI中引用了多个状态,则p方法中也会包含多个if语句:
// UI中引用多个状态
<h1 on:click="{count0++}">{count0}</h1>
<h1 on:click="{count1++}">{count1}</h1>
<h1 on:click="{count2++}">{count2}</h1>
对应p方法包含多个if语句:
p(new_ctx, [dirty]) {
ctx = new_ctx;
if (dirty & /*count*/ 1) set_data(t0, /*count*/ ctx[0]);
if (dirty & /*count1*/ 2) set_data(t2, /*count1*/ ctx[1]);
if (dirty & /*count2*/ 4) set_data(t4, /*count2*/ ctx[2]);
},
Demo2完整的更新步骤如下:
- 点击
H1触发回调函数update update内调用$$invalidate,更新ctx中的count,标记count为dirty,调度更新- 执行
p方法,进入dirty的项(即count)对应if语句,执行更新对应DOM Element的方法
以上就是JavaScript开发Svelte实现原理详解的详细内容,更多关于Svelte实现原理的资料请关注我们其它相关文章!
本文标题为:JavaScript开发简单易懂的Svelte实现原理详解
- 深入浅析AjaxFileUpload实现单个文件的 Ajax 文件上传库 2022-12-15
- javascript 判断当前浏览器版本并判断ie版本 2023-08-08
- 1 Vue - 简介 2023-10-08
- vue keep-alive 2023-10-08
- layui数据表格以及传数据方式 2022-12-13
- 基于CORS实现WebApi Ajax 跨域请求解决方法 2023-02-14
- jsPlumb+vue创建字段映射关系 2023-10-08
- JS实现左侧菜单工具栏 2022-08-31
- ajax实现输入提示效果 2023-02-14
- 关于 html:如何从 css 表中删除边距和填充 2022-09-21
