这是我对DOM和浏览器如何工作的幼稚理解
只要DOM中的某些内容(真实dom)发生变化,浏览器就会重新绘制或重排DOM。因此,用更简单的术语来说,每次DOM更改时,浏览器都需要重新计算CSS,进行布局并重新绘制网页。这是在真正的dom中花费时间的原因。
因此,React随此虚拟DOM一起提供,它的实际作用是将更改批量进行批处理,然后调用一次将其应用于实际域。因此,最大程度地减少了回流和重涂。
那斯维尔特呢。如果直接操作DOM,它将如何控制浏览器的重绘/重排。
两个库都将需要对dom进行的更改减至最少。区别在于他们找出最小变化集的方式。
React的方法是在内存中表示dom(虚拟dom)的表示形式。设置状态后,它将再次运行渲染过程以创建另一个虚拟dom。它比较前后的差异,找到更改的内容,然后将所有更改推送到真实的dom。
Svelte的方法是,当您设置变量时,它会设置一个标志,将该变量标记为已更改。它知道哪些变量依赖于其他变量,因此它逐步浏览所有因变量并重新计算它们,从而建立了需要更改的内容的列表。然后将这些更改推送到dom。
,除了上面的(正确)答案之外:Svelte还可以“编译”您提供给它的代码,因此最终代码可以在没有库运行时的情况下执行(与React相比)。而且它创建了易于阅读的代码,因此绝对有可能了解其内部工作原理。
注意:这将是更长的答案-仍然遗漏了有关斯维尔特(Svelte)幕后发生的事情的许多细节。但我希望这有助于揭开某些幕后神秘面纱。同样,这也是Svelte从v3.16.x开始执行操作的方式。由于这是内部的,因此可能会更改。但是,我仍然发现了解实际情况总是很有意义的。
所以,我们开始。
最重要的是:Svelte教程具有一项有用的功能,可让您查看生成的代码(在“结果”窗格旁边)。乍一看可能有点吓人,但很快您就可以掌握它了。
以下代码将基于此示例(但经过进一步简化):Svelte tutorial - reactivity/assignments
我们的示例组件定义(即 App.svelte )如下所示:
<script>
let count = 0;
function handleClick() {
count += 1;
}
</script>
<button on:click={handleClick}>{count}</button>
基于该组件定义,Svelte编译器创建一个函数,该函数将创建一个“片段”,该片段接收“上下文”并与之交互。
function create_fragment(ctx) {
let button;
let t;
let dispose;
return {
c() {
button = element("button");
t = text(/*count*/ ctx[0]);
dispose = listen(button,"click",/*handleClick*/ ctx[1]);
},m(target,anchor) {
insert(target,button,anchor);
append(button,t);
},p(ctx,[dirty]) {
if (dirty & /*count*/ 1) set_data(t,/*count*/ ctx[0]);
},i: noop,o: noop,d(detaching) {
if (detaching) detach(button);
dispose();
}
};
}
片段负责与DOM交互,并将随组件实例一起传递。简而言之,
中的代码注意:诸如 element 或 set_data 之类的功能实际上很容易实现。 例如,功能 element 只是 document.createElement :
的包装。function element(name) {
return document.createElement(name);
}
上下文(ctx)将包含所有实例变量以及函数。它不过是一个简单的数组。由于Svelte在编译时“知道”每个索引的含义,因此它可以在其他地方硬引用这些索引。
此代码实质上定义了实例上下文:
function instance($$self,$$props,$$invalidate) {
let count = 0;
function handleClick() {
$$invalidate(0,count += 1);
}
return [count,handleClick];
}
实例方法和 create_fragment 都将通过另一个函数调用 init 进行调用。它涉及的内容更多,因此,您可以查看此link to the source。而不是在此处复制和粘贴。
$$ invalidate 将确保将 count 变量设置为脏变量并安排更新。当下一次更新运行时,它将查看所有“脏”组件并进行更新。这是如何发生的实际上更多是实现细节。如果有兴趣,请在flush function中设置一个断点。
实际上,如果您确实想更深入一点,我建议克隆template应用程序,然后创建一个简单的组件,对其进行编译,然后检查“ bundle.js”。如果您删除源映射或将其禁用,也可以调试实际代码。
因此,例如,如下设置rollup.config.js:
output: {
sourcemap: false,format: 'iife',name: 'app',file: 'public/build/bundle.js'
},plugins: [
svelte({
dev: false,
注意:如上所示,我建议还将 dev模式设置为 false ,因为这将创建更简洁的代码。
一个简洁的功能:我们的应用运行之后,您还可以访问 app 变量(由于将其捆绑为一个立即窗口,因此该变量已分配给全局窗口对象-调用函数表达式)。
因此,您可以打开控制台并简单地说
console.dir(app)
会产生类似这样的结果
App
$$:
fragment: {c: ƒ,m: ƒ,p: ƒ,i: ƒ,o: ƒ,…}
ctx: (2) [0,ƒ]
props: {count: 0}
update: ƒ noop()
not_equal: ƒ safe_not_equal(a,b)
bound: {}
on_mount: []
on_destroy: []
before_update: []
after_update: []
context: Map(0) {}
callbacks: {}
dirty: [-1]
__proto__: Object
$set: $$props => {…}
一个很酷的功能是您可以自己 $ set 方法来更新实例。例如这样的
app.$set({count: 10})
也有Svelte DevTools试图使Svelte的内部结构更加平易近人。当我亲自试用它们时,它们似乎会以某种方式影响我的应用程序的渲染性能,因此我不会自己使用它们。但是肯定值得一看。
好吧,你在那里。我知道这仍然是技术性的,但是我希望它有助于更好地了解Svelte编译代码的功能。