13
头图

Hi everyone, I'm Kasong.

0618cbf8135037 has been out for a long time, and I have always wanted to write an Svelte

This article will focus on a flow chart and two Demo explanations. The correct way to eat is to open this article with a computer, follow the flow chart, Demo while watching, typing, and learning.

Let me start.

Demo1

The implementation principle of Svelte

In the picture, Component is a component written by the developer, and the internal dashed part is Svelte compiler. The arrows in the figure are the workflow at runtime.

First look at the compilation, consider the following App component code:

<h1>{count}</h1>

<script>
  let count = 0;
</script>
For the complete code, see Demo1 repl

The browser will display:

This code is compiled by the compiler to produce the following code, including three parts:

  • create_fragment Method
  • count statement
  • class App statement
// 省略部分代码…
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

First look at the create_fragment method, which is compiled by the compiler based on App of UI , which provides a method for the component to interact with the browser. In the above compilation result, there are 3 methods:

  • c , on behalf of create , used to create the corresponding DOM Element according to the template content. In the example, create H1 corresponding to DOM Element :
h1 = element("h1");
h1.textContent = `${count}`;
  • m , representing mount , used to insert c created by DOM Element into the page to complete the first rendering of the component. In the example, H1 will be inserted into the page:
insert(target, h1, anchor);

insert method will call target.insertBefore :


function insert(target, node, anchor) {
  target.insertBefore(node, anchor || null);
}
  • d , which stands for detach , is used to remove the component corresponding to DOM Element from the page. In the example, H1 will be removed:
if (detaching) detach(h1);

detach method will call parentNode.removeChild :

function detach(node) {
  node.parentNode.removeChild(node);
}

If you carefully observe the flowchart, you will find that the product compiled by the App p method fragment in the figure.

This is because App does not have change state , so the corresponding method will not appear in the compiled product.

Can be found, create_fragment returned c , m method for rendering components for the first time. So who is calling these methods?

SvelteComponent

Each component corresponds to a SvelteComponent inherited from 0618cbf813575a. class init method will be called to complete the component initialization, and create_fragment will be called init

class App extends SvelteComponent {
  constructor(options) {
    super();
    init(this, options, null, create_fragment, safe_not_equal, {});
  }
}

To sum up, the result of the compilation of the dotted line in the flow chart in Demo1

  • fragment : compiled to the return value create_fragment
  • UI : create_fragment return value m execution result of the method
  • ctx : Represents the context of the component. Since the example only contains a state that will not change count , ctx is the statement statement for count

Demo that can change status

Now modify Demo , add update method, bind the click event H1 count change:

<h1 on:click="{update}">{count}</h1>

<script>
  let count = 0;
  function update() {
    count++;
  }
</script>
For the complete code, see Demo2 repl

The compilation product changes, ctx changes as follows:

// 从module顶层的声明语句
let count = 0;

// 变为instance方法
function instance($$self, $$props, $$invalidate) {
  let count = 0;

  function update() {
    $$invalidate(0, count++, count);
  }

  return [count, update];
}

count from module declaration top becomes instance variables within the method. The reason for this change is that App can instantiate multiple:

// 模版中定义3个App
<App/>
<App/>
<App/>

// 当count不可变时,页面渲染为:<h1>0</h1>
<h1>0</h1>
<h1>0</h1>

When count is immutable, all App can reuse the same count . But when count variable, depending App , the page may render as:

<h1>0</h1>
<h1>3</h1>
<h1>1</h1>

So each App needs to have an independent context save count , which is the meaning of the instance In general, the Svelte compiler will track all variable declarations in <script>

  • Whether to include the statement to change the variable, such as count++
  • Whether to include reassignment statements, such as count = 1
  • Wait for the situation

Once found, the variable will be extracted to instance , the return value after the execution of instance ctx .

At the same time, if the statement that performs the above operations can be referenced through the template, the statement will be wrapped $$invalidate

In Demo2 , the update method satisfies:

  • It includes changing count statement - count++
  • Can be referenced through the template-as a click callback function

So compiled update change within count statements are $$invalidate parcel method:

// 源代码中的update
function update() {
  count++;
}

// 编译后instance中的update
function update() {
  $$invalidate(0, count++, count);
}

As can be seen from the flowchart, the $$invalidate method will perform the following operations:

  • Update the ctx of the saved state in count++ Demo2
  • Mark dirty , that is, all count related to App UI
  • Schedule update, microtask macrotask executed in the same $$invalidate will be executed uniformly after the execution of macrotask p method in the fragment

p method Demo2 the new compiler product, in addition p addition, create_fragment conventional process also produces a corresponding change in:

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 method executes $$invalidate labeled dirty corresponding to the entry of the update function.

In Demo2 in, App UI only cited state count , so update method is only one if statement, if UI referenced in multiple states, the p method will also contain multiple if statement:

// UI中引用多个状态 
<h1 on:click="{count0++}">{count0}</h1>
<h1 on:click="{count1++}">{count1}</h1>
<h1 on:click="{count2++}">{count2}</h1>

The method corresponding to p contains multiple if statements:

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 complete update steps for 0618cbf8135f8a are as follows:

  1. Click H1 trigger the callback function update
  2. update calls within $$invalidate , update ctx in count , mark count as dirty , schedule update
  3. Execute the p method, enter the item of dirty count ) corresponds to the if statement, execute the update method DOM Element

Summarize

Svelte will be much more complicated, but the core implementation is like this.

We can intuitively feel, by means of constraint template syntax, compiled optimized to establish a direct state to change the correspondence between the DOM node .

In Demo2 , the change of count directly corresponds to a if statement in the p Svelte perform the fine-grained update , which has more performance advantages than the virtual DOM framework.

Analysis of the above properties in the fourth row SELECT Row is a update granular . For comparison, the React (the third last column) is much worse.

Welcome to join the human high-quality front-end framework group , take flight


卡颂
3.1k 声望16.7k 粉丝