SvelteJS vs ReactJS 渲染区别(重绘/回流)

Posted

技术标签:

【中文标题】SvelteJS vs ReactJS 渲染区别(重绘/回流)【英文标题】:SvelteJS vs ReactJS rendering difference (repaint / reflow) 【发布时间】:2020-03-13 02:29:31 【问题描述】:

这是我对 DOM 和浏览器工作原理的幼稚理解

只要 DOM(真实 dom)中的某些内容发生更改,浏览器就会重绘或重排 DOM。因此,简单来说,每次 DOM 更改时,浏览器都需要重新计算 CSS,进行布局并重新绘制网页。这就是在现实世界中需要时间的东西。

所以 React 带有这个虚拟 DOM,它实际上做的是批量更改并调用一次将它们应用到真实 DOM。因此,最大限度地减少了回流和重绘。

那么 Svelte 呢?如果它直接操作 DOM,它如何控制浏览器的重绘/重排。

【问题讨论】:

【参考方案1】:

除了上面的(正确)答案: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();
        
    ;

fragment 负责与 DOM 交互,并将与组件实例一起传递。简而言之,里面的代码

"c" 将在 create 上运行(在内存中创建 DOM 元素并设置事件处理程序) “m”将在 mount 上运行(将元素附加到 DOM) “p”将在更新时运行,即当某些东西(包括道具)发生变化时 “i”/“o”与 intro/outro 相关(即过渡) "d" 将在 destroy 上运行

注意:elementset_data 之类的函数实际上非常平易近人。 例如,函数 element 只是 document.createElement 的包装器:

function element(name) 
    return document.createElement(name);

context (ctx) 将包含所有实例变量和函数。它只不过是一个简单的数组。由于 Svelte 在编译时“知道”每个索引的含义,因此它可以硬引用其他地方的索引。

这段代码本质上定义了实例上下文:

function instance($$self, $$props, $$invalidate) 
    let count = 0;

    function handleClick() 
        $$invalidate(0, count += 1);
    

    return [count, handleClick];

instance 方法和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 mode 设置为 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 代码在做什么。

【讨论】:

非常详细易懂。谢谢【参考方案2】:

这两个库都最大限度地减少了需要对 dom 进行的更改。不同之处在于他们找出最小更改集的方式。

React 的方法是在内存中表示 dom(虚拟 dom)。当您设置状态时,它会再次运行渲染过程以创建另一个虚拟 dom。它比较之前和之后,找出发生了什么变化,然后将任何变化推送到真实的 dom。

Svelte 的方法是,当您设置一个变量时,它会设置一个标志,将该变量标记为已更改。它知道哪些变量依赖于其他变量,因此它会逐步遍历任何因变量并重新计算它们,从而建立一个需要更改的列表。然后这些更改被推送到 dom。

【讨论】:

是的,我有点明白它的要点。但我期待更多的幕后工作和更多的内部运作

以上是关于SvelteJS vs ReactJS 渲染区别(重绘/回流)的主要内容,如果未能解决你的问题,请参考以下文章

reactjs 渲染状态或道具

10ReactJs基础知识10--组件组合 vs 继承

sveltejs:如何更改 sveltejs 样板 rollup.config.js 中的 ip 和主机?

reactjs-mobile常用组件

reactjs怎么从外部引入组件

如何用reactjs构建一个完整的前端页面