laitimes

This picture makes the source code of vue3 clear!

author:Speed Starry Sky 4DO

Recently, when a student was learning the source code of Vue3, he organized most of the core logic of Vue3 into a mind map:

This picture makes the source code of vue3 clear!

The content of the collation is very detailed. It should be useful to anyone who is still learning the Vue3 source code

There are two things we need to do before we get to know the Vue3 framework design, and these two things are the main ones today.

  1. We need to synchronize and clarify the concepts of some words, such as: declarative, imperative, runtime, compile-time... These terms will be frequently touched upon later in the framework design.
  2. We need to understand some basic concepts about front-end frameworks. The design principles of the framework, the developer development experience principles. This is a way to help you solve some inherent doubts and demystify Vue.

So ready to go?

Here we go!

02: Imperative programming of the programming paradigm

For today's front-end development, there are two main programming paradigms:

  1. Imperative programming
  2. Declarative programming

These two paradigms are generally relative.

Imperative Expressions

So first of all, let's talk about what is called imperative.

Specific examples:

Zhang San's mother asked Zhang San to buy soy sauce.

So how did Zhang San do it?

Zhang San picked up the money

Open the door

Went downstairs

to the store

Take the money to buy soy sauce

Back home

The above process describes in detail what Zhang San did at every step in the process of buying soy sauce. So a way of describing the process in detail can be called imperative.

So what if you put this approach into a concrete code implementation?

Let's look at one of the following things:

Show "hello world" in the specified div

So if we want to accomplish something like this, how do we do it in a commanding way?

We know that the core of imperative is this: focus on the process.

Therefore, the above things can be implemented imperatively, and the following logic and code can be obtained:

// 1. 获取到指定的 div
const divEle = document.querySelector('#app')
// 2. 为该 div 设置 innerHTML 为 hello world
divEle.innerHTML = 'hello world'
           

Although the code is only two steps, it clearly describes the process that needs to be taken to accomplish this

So if what we're doing becomes more complex, the whole process becomes more complicated.

Like what:

For the child element of the specified div The child element p tag of the div, showing the variable msg

Then if you complete the above functions through imperatives, you will get the following logic and code:

// 1. 获取到第一层的 div
const divEle = document.querySelector('#app')
// 2. 获取到它的子 div
const subDivEle = divEle.querySelector('div')
// 3. 获取第三层的 p
const subPEle = subDivEle.querySelector('p')
// 4. 定义变量 msg
const msg = 'hello world'
// 5. 为该 p 元素设置 innerHTML 为 hello world
subPEle.innerHTML = msg
           

Through the above examples, I believe you can have a basic understanding of the concept of imperative.

Finally, to summarize, what is imperative?

Imperative is a programming paradigm that focuses on the process and describes the detailed logic and steps to accomplish a function.

03: Declarative programming of the programming paradigm

Now that we've learned about imperatives, let's move on to declarative programming.

As far as the declarative style is concerned, everyone is actually very familiar with it.

For example, the following code is a typical declarative form:

<div>{{ msg }}</div>
           

Does this code sound familiar to you?

That's right, that's the double brace syntax that's very common in Vue. So when we write Vue template syntax, we're actually writing declarative programming all along.

So what exactly does declarative programming mean?

Let's take the example from above:

Zhang San's mother asked Zhang San to buy soy sauce.

So how did Zhang San do it?

Zhang San picked up the money

Open the door

Went downstairs

to the store

Take the money to buy soy sauce

Back home

In this example, we say: what Zhang San is doing is imperative. So what Zhang San's mother did was declarative.

In such a matter, Zhang San's mother just issued a statement, she didn't care about how Zhang San went to buy the soy sauce, only the final result.

Therefore, the so-called declarative formula refers to a paradigm that does not focus on the process, but only on the result.

Again, if we use code to represent this, here's an example:

For the child element of the specified div The child element p tag of the div, showing the variable msg

The result is the following code:

<div id="app">
  <div>
    <p>{{ msg }}</p>
  </div>
</div>
           

In such code, we don't care at all how msg is rendered into the p tag, all we care about is that in the p tag, the specified text is rendered.

Finally, to summarize, what is declarative?

Declarative is a programming paradigm that focuses on the outcome and is not concerned with the detailed logic and steps to complete a function. (Note: This doesn't mean that declarative doesn't need a process!) Declarative just hides the process! )

04: Imperative VS Declarative

So after we have explained imperative and declarative, many students will definitely make a comparison between these two programming paradigms.

Is it imperative? Or is it better to be declarative?

So in order to understand this problem, we first need to figure out, what are the criteria for evaluating a programming paradigm as good or bad?

In general, we evaluate a programming paradigm in two ways:

  1. performance
  2. maintainability

So let's analyze imperative and declarative through these two aspects.

performance

Performance has always been a special focus when we are developing projects, so how do we usually describe the performance of a feature?

Let's look at an example:

Set the text to "hello world" for the specified div

The simplest code for this requirement is:

div.innerText = "hello world" // 耗时为:1
           

You shouldn't find a simpler code implementation than this.

So let's compare the time of this operation to :1. (PS: The less time it takes, the more performance it takes)

Then let's look at the declarative formula, which reads:

<div>{{ msg }}</div>  <!-- 耗时为:1 + n -->
<!-- 将 msg 修改为 hello world -->
           

So: we know that the easiest way to modify text is innerText, so no matter how the declarative code implements the text switch, then it must take > 1, which we compare to 1 + n (compared to the performance cost).

So, from the above examples, we can see that imperative performance > declarative performance

maintainability

Maintainability represents many dimensions, but in general, the so-called maintainability refers to the code that can be easily read, modified, deleted, and added.

To achieve this goal, to put it bluntly: the logic of the code should be simple enough for people to understand at a glance.

Now that we've clarified this concept, let's take a look at the code logic of imperative and declarative in the same business:

// 命令式
// 1. 获取到第一层的 div
const divEle = document.querySelector('#app')
// 2. 获取到它的子 div
const subDivEle = divEle.querySelector('div')
// 3. 获取第三层的 p
const subPEle = subDivEle.querySelector('p')
// 4. 定义变量 msg
const msg = 'hello world'
// 5. 为该 p 元素设置 innerHTML 为 hello world
subPEle.innerHTML = msg
           
// 声明式
<div id="app">
  <div>
    <p>{{ msg }}</p>
  </div>
</div>
           

For the above code, declarative code is significantly easier to read, and therefore more maintainable.

So, from the above examples, we can see that **imperative maintainability< declarative maintainability**

To sum up

From the above analysis, we can see two points:

  1. Imperative performance > declarative performance
  2. Imperative maintainability < Declarative maintainability

So both sides have their own advantages and disadvantages, which paradigm should we use in our daily development?

To understand this, we need to understand more.

05: Principles of enterprise application development and design

Why are the design principles of enterprise applications complicated to describe?

Because for different enterprise types (large factories, small and medium-sized factories, personnel outsourcing, project outsourcing), and different project types (front office, middle office, back office), there may be some differences in the corresponding enterprise application design in principle.

Therefore, the description we make here will abandon some subtle differences and only focus on the core points to elaborate.

No matter what type of business, and no matter what type of project they are developing, then the main focus is nothing more than two:

  1. Project cost
  2. Develop experiences

Project cost

The cost of the project is very easy to understand, it determines the price paid by a company to complete "this thing", and thus directly determines whether the project is profitable or not (with the exception of the cash-burning projects of large factories).

So since the project cost is so important, you can think about it, what determines the project cost?

That's right! It's your development cycle.

The longer the development cycle, the higher the personnel costs will be, which will lead to higher project costs.

From our previous analysis, we can see that the declarative development paradigm is more maintainable than imperative.

Maintainability is determined to a certain extent, which will shorten the development cycle of the project and make it easier to upgrade, thus saving a lot of development costs.

So that's why Vue is becoming more and more popular.

Develop experiences

The core elements that determine the developer's development experience are mainly the difficulty of development and reading, which is called mental burden.

Mental burden can be used as a criterion to measure the difficulty of development, high mental burden indicates that the difficulty of development is higher, and low mental burden means that the difficulty of development is lower and development is more comfortable.

Then, as we said before, declarative development is significantly less difficult than imperative development.

Therefore, for the development experience, the declarative development experience is better, that is, the mental burden is lower.

06: Why is the framework design process a trade-off?

Vue author Yuxi You said in a talk that the design process of the framework is actually a process of trade-offs.

What does this mean?

If you want to understand this, let's clarify the concept mentioned earlier:

  1. Imperative performance > declarative performance
  2. Imperative maintainability < Declarative maintainability
  3. Declarative frameworks are inherently imperative code
  4. When developing enterprise projects, most of them use declarative frameworks

Now that we've identified such a question, let's think about the question: what are the principles of framework development and design?

We know that for Vue, when we use it, it's done declaratively, but internally, it's imperative.

So we can think of it as follows: Vue encapsulates imperative logic and exposes declarative interfaces

So in that case, we know that imperative performance > declarative performance. So why would Vue choose a declarative solution?

In fact, the reason is very simple, that is: imperative maintainability < declarative maintainability.

For the child element of the specified div The child element p tag of the div, showing the variable msg

Take this example, for example.

For developers, they don't need to focus on the implementation process, they only need to focus on the final result.

For Vue, all he needed to do was encapsulate imperative logic with as little performance loss as possible! It needs to find a balance between performance and maintainability. So as to find a point with better maintainability and better performance.

So for Vue, it's designed to be as serviceable as possible with minimal performance loss.

So back to our title: Why is the design process of a framework a trade-off?

The answer is just around the corner, because:

We need to find a balance between maintainability and performance. On the basis of ensuring maintainability, the loss of performance is reduced as much as possible.

So the design process of the framework is really a constant trade-off between maintainability and performance

07: What is a runtime?

In Vue 3's source code, there is a runtime-core folder that contains the core code logic of the runtime.

runtime-core 中对外暴露了一个函数,叫做 渲染函数render

We can do the rendering of the DOM by using render instead of template:

Some students may not understand what the current code means, it doesn't matter, it doesn't matter, we'll talk about it in detail later.
<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
</head>

<body>
  <div id="app"></div>
</body>

<script>
  const { render, h } = Vue
  // 生成 VNode
  const vnode = h('div', {
    class: 'test'
  }, 'hello render')

  // 承载的容器
  const container = document.querySelector('#app')

  // 渲染函数
  render(vnode, container)
</script>
           

We know that in Vue's project, we can render DOM nodes with tempalte, as follows:

<template>
	<div class="test">hello render</div>
</template>
           

But for the render example, instead of using tempalte, we use a function called render that returns something that I don't know what it is, so why can we render the DOM as well?

With this question in mind, let's take a look:

We know that in the code above, there is a core function: the render function, so what exactly does this render do here?

Let's take a look at an example of code:

Suppose one day your leader tells you:

I would like to base it on the following data:

Render a div like this:

{
 type: 'div',
 props: {
  class: test
 },
 children: 'hello render'
}
           
<div class="test">hello render</div>
           

So how would you implement such a requirement? You can think about it for a while, try to implement it, and then we can move on..........

So let's implement the following code based on this requirement:

<script>
  const VNode = {
    type: 'div',
    props: {
      class: 'test'
    },
    children: 'hello render'
  }
  // 创建 render 渲染函数
  function render(vnode) {
    // 根据 type 生成 element
    const ele = document.createElement(vnode.type)
    // 把 props 中的 class 赋值给 ele 的 className
    ele.className = vnode.props.class
    // 把 children 赋值给 ele 的 innerText
    ele.innerText = vnode.children
    // 把 ele 作为子节点插入 body 中
    document.body.appendChild(ele)
  }

  render(VNode)
</script>
           

In one of these codes, we've managed to render the corresponding DOM with a render function, and similar to the previous render example, they both render a vnode, which you think is really wonderful!

But after using your render for a while, your leader said: It's too troublesome to write like this every day, and you have to write a complex vnode every time, can you just write the HTML tag structure for you to render?

After thinking about it, you say: if that's the case, then it's not something that the above runtime code can solve!

That's right! One such "framework" we just wrote is the runtime code framework.

So finally, let's make a summary: the runtime can use render to render the vnode as a real DOM node.

08: What is compile-time?

As we made clear, there is no way to parse HTML tags by using the runtime alone.

To do this, we need to use another thing, which is compile-time.

When compiling in Vue, it would be more accurate to say "compiler". Its code exists mainly under the compiler-core module.

Let's take a look at the following code:

<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="https://unpkg.com/[email protected]/dist/vue.global.js"></script>
</head>

<body>
  <div id="app"></div>
</body>

<script>
  const { compile, createApp } = Vue

  // 创建一个 html 结构
  const html = `
    <div class="test">hello compiler</div>
  `
  // 利用 compile 函数,生成 render 函数
  const renderFn = compile(html)

  // 创建实例
  const app = createApp({
    // 利用 render 函数进行渲染
    render: renderFn
  })
  // 挂载
  app.mount('#app')
</script>

</html>
           

For the compiler, its main purpose is to compile the HTML in the template into a render function. Then use the runtime to mount the corresponding DOM via render.

So finally, let's make a summary: you can compile the HTML node into a render function during compilation

09: Runtime + Compilation

In the first two sections, we've looked at runtime and compile-time, respectively, and we also know that vue is a runtime + compile-time framework!

Vue mounts the real DOM by parsing the HTML template through the compiler, generating the render function, and then parsing the render through the runtime.

So some students may have doubts when they see this, since the compiler can directly parse the html template, then why do you need to generate the render function and then render it? Why not just use the compiler for rendering?

Namely: why should Vue be designed as a runtime + compile-time framework?

To get to the bottom of this, we need to know how DOM rendering works.

For DOM rendering, it can be divided into two parts:

  1. For the first render, we can call it mounting
  2. Updating the rendering, we can call it patching

Primary Dyeing

So what is an initial render?

When the innerHTML of the initial div is empty,

<div id="app"></div>
           

We render the following node in the div:

<ul>
 <li>1</li>
 <li>2</li>
 <li>3</li>
</ul>
           

Such a render is the initial render. In such a single render, we generate a UL tag, generate three LI tags, and mount them into a div.

Update the rendering

So at this point, if the content of the UL tag has changed:

<ul>
 <li>3</li>
 <li>1</li>
 <li>2</li>
</ul>
           

li-3 has risen to the top of the list, so you can wonder: how do we expect the browser to update this rendering?

There are two ways to render this browser update:

  1. Delete all the original nodes and re-render the new nodes
  2. Delete the li-3 in the original location and insert the li-3 in the new location

So which of the two ways do you think is better? So let's break it down:

  1. First of all, for the first method: the advantage is that you don't need to do any comparisons, you need to perform 6 times (3 times to delete, 3 times to re-render) DOM processing.
  2. For the second approach: it is logically more complicated. He needs to do it in two steps:
    1. Compare the differences between the old node and the new node
    2. Depending on the difference, delete an old node and add a new node

So based on the above analysis, we know:

  1. The first way: there are more DOM operations involved
  2. The second way: it will involve JS computation + a small amount of DOM operations

So which of the two ways is faster? Let's experiment:

const length = 10000
  // 增加一万个dom节点,耗时 3.992919921875 ms
  console.time('element')
  for (let i = 0; i < length; i++) {
    const newEle = document.createElement('div')
    document.body.appendChild(newEle)
  }
  console.timeEnd('element')

  // 增加一万个 js 对象,耗时 0.402099609375 ms
  console.time('js')
  const divList = []
  for (let i = 0; i < length; i++) {
    const newEle = {
      type: 'div'
    }
    divList.push(newEle)
  }
  console.timeEnd('js')
           

As you can see from the results, DOM operations are much more time-consuming than JS operations, i.e., DOM operations are more performance-intensive than JS.

So according to such a conclusion, back to the scenario we just said:

First of all, for the first option: the advantage is that you don't need to do any comparisons, just 6 DOM processes (3 times for deletion and 3 times for re-rendering).

For the second approach: it is logically more complicated. He needs to do it in two steps:

Compare the differences between the old node and the new node

Depending on the difference, delete an old node and add a new node

According to the conclusion, method 1 will consume more performance than method 2 (i.e., the performance is worse).

So with that conclusion in mind, let's go back to the original question: why was Vue designed as a runtime + compile-time framework?

Answer:

  1. For runtime-only purposes: since there is no compiler, we can only provide a complex JS object.
  2. For pure compile-time: because of the lack of a runtime, it can only be done at compile time, and because the runtime is omitted, it may be faster. However, this would be a loss of flexibility (see Chapter 6 of the Virtual DOM for details, or click here for an official example). Svelte, for example, is a pure compile-time framework, but it may not run as fast as it could theoretically.
  3. Runtime + Compile: Vue or React, for example, are built in this way to achieve a balance of flexibility and performance optimization.

10: What are the side effects

In Vue's source code, there's a lot of one concept involved, and that's side effects.

So we need to understand what side effects mean.

A side effect is a set of consequences when we set or getter data.

So what does that mean exactly? Let's talk about each of them:

Puts

A setter represents an assignment operation, for example, when we execute code like this:

msg = '你好,世界'
           

At this point, msg triggers a setter behavior.

So let's say that msg is a responsive data, then such a data change will affect the corresponding view change.

Then we can say that MSG's setter behavior triggered a side effect that caused the view to follow to change.

getter

The getter represents a value operation, for example, when we execute code like this:

element.innerText = msg
           

In this case, a getter operation is triggered for the variable msg, and such a value operation will also cause the innerText of the element to change.

So we can say that msg's getter behavior triggered a side effect that caused element's innterText to change.

Will there be more than one side effect?

Now that you've clarified the basic concept of side effects, let's think about it: Is there likely to be more than one side effect?

The answer is: Yes.

Here's a simple example:

<template>
  <div>
    <p>姓名:{{ obj.name }}</p>
    <p>年龄:{{ obj.age }}</p>
  </div>
</template>

<script>
 const obj = ref({
    name: '张三',
    age: 30
  })
  obj.value = {
    name: '李四',
    age: 18
  }
</script>
           

In one such code, obj.value triggers a setter behavior, but causes the contents of both p tags to change, i.e., two side effects.

A little bit

From this subsection we know:

  1. A side effect is a set of consequences when a setter or getter operation is performed on the data
  2. Side effects may be multiple.

11:View 3 框架设计概述

Based on the previous learning, we already know:

  1. What is declarative
  2. What is Imperative
  3. What is Runtime
  4. What is compile-time
  5. What is runtime + compile-time
  6. At the same time, I also know that the design process of the framework itself is a process of trade-offs

So with that out of the way, let's take a look at a basic framework design for Vue3:

For Vue3, the core can be roughly divided into three main modules:

  1. 响应性:reactivity
  2. Runtime: runtime
  3. 编译器:compiler

Let's describe the basic relationship between the three with the following basic structure:

<template>
	<div>{{ proxyTarget.name }}</div>
</template>

<script>
import { reactive } from 'vue'
export default {
	setup() {
		const target = {
			name: '张三'
		}
		const proxyTarget = reactive(target)
		return {
			proxyTarget
		}
	}
}
</script>
           

In the above code:

  1. First, we declare a reactive data using the reactive method.
    1. This method is a way for the reactivity module to be exposed to the outside world
    2. It can receive a complex data type, as a proxy (now many students may not understand what a proxy is, it doesn't matter if we will introduce it in detail later, now you just need to have an impression) of the proxy object (target)
    3. Returns a proxy object of type (proxyTarget)
    4. When proxyTarget triggers a setter or getter behavior, it will have a side effect
  2. Then, in the tempalte tag, we write a div. We know that the HTML written here is not real HTML, we can call it a template, and the content of the template will be compiled by the compiler to generate a render function
  3. Finally, Vue uses the runtime to execute the render function to render the real DOM

以上就是 reactivity、runtime、compiler 三者之间的运行关系。