這張圖,把vue3 的源碼講清楚了! ! !

2024.07.02

整理的內容非常詳細。應該會對所有還在學習vue3 原始碼的同學都有所幫助。所以分享給大家吧!

那麼今天,咱們就借助這位同學的腦圖作為契機,來為大家捋一捋 【Vue3 框架設計原理】(看完設計原理之後,再看腦圖收穫會更大哦~)

01:前言

在了解 Vue3 框架設計之前,我們需要做兩件事情,而這兩件事也是今天的主要內容。

  1. 我們需要同步並明確一些詞彙的概念,例如:聲明式、命令式、執行時間、編譯時...。這些詞彙將會在後面的框架設計中被經常涉及。
  2. 我們需要了解一些關於 前端框架 的一些基礎的概念。框架的設計原則,開發者發展體驗原則。以此來幫助大家解決一些固有的疑惑,從而揭開 vue 神秘的面紗。

那麼準備好了?

我們開始吧!

02:程式設計範式之命令式編程

針對於目前的前端開發而言,主要存在兩種 程式設計範式:

  1. 命令式程式設計
  2. 聲明式程式設計

這兩種 範式 一般是相對來去說的。

命令式

那麼首先我們先來說什麼叫做 命令式。

具體例子:

張三的媽媽讓張三去買醬油。

那麼張三怎麼做的呢?

  1. 張三拿起錢
  2. 打開門
  3. 下了樓
  4. 到商店
  5. 拿錢買醬油
  6. 回到家

以上的流程詳細的描述了,張三在買醬油的過程中,每一步都做了什麼。那麼這樣一種:詳細描述做事過程 的方式就可以被叫做 命令式。

那如果把這樣的方式放到具體的程式碼實現之中,又該怎麼做呢?

我們來看以下這樣的一個事情:

在指定的div 中展示“hello world”

那如果想要完成這樣的事情,透過命令式的方式我們要如何實現呢?

我們知道命令式的核心在於:關注過程。

所以,以上事情透過命令式實作則可得出以下邏輯與程式碼:

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

雖然程式碼只有兩步,但是它清楚的描述了:完成這件事情,所需要經歷的過程

那麼假如我們所做的事情,變得更加複雜了,整個過程也會變得更加複雜。

比如:

為指定的div 的子元素div 的子元素p 標籤,展示變數msg

那麼透過命令式完成以上功能,則會得出以下邏輯與程式碼:

// 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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.

那麼透過以上例子,相信大家可以對命令式的概念有了一個基礎的認識。

最後做一個總結,什麼叫做命令式呢?

命令式是:關注過程 的一種程式設計範式,他描述了完成一個功能的 詳細邏輯與步驟。

03:程式設計範式之聲明式編程

當了解完命令式之後,那麼接下來我們就來看 聲明式 程式設計。

針對於聲明式而言,大家其實都是非常熟悉的了。

例如以下程式碼,就是一個典型的 聲明式 :

<div>{{ msg }}</div>
  • 1.

對於這個程式碼,大家是不是覺得有些熟悉?

沒錯,這就是 Vue 中非常常見的雙大括號語法。所以當我們在寫 Vue 模板語法 的時候,其實一直寫的就是 聲明式 程式設計。

那麼聲明式程式設計具體指的是什麼意思呢?

還是以剛才的例子為例:

張三的媽媽讓張三去買醬油。

那麼張三怎麼做的呢?

  1. 張三拿起錢
  2. 打開門
  3. 下了樓
  4. 到商店
  5. 拿錢買醬油
  6. 回到家

在這個例子中,我們說:張三所做的事情就是命令式。那麼張三媽媽所做的事情就是 聲明式。

在這樣一個事情中,張三媽媽只是發布了一個聲明,她並不關心張三如何去買的醬油,只關心最後的結果。

所以說,所謂聲明式指的是:不關注過程,只關注結果 的範式。

同樣,如果我們透過程式碼來進行表示的話,以下例子:

為指定的div 的子元素div 的子元素p 標籤,展示變數msg

將會得出以下程式碼:

<div id="app">
  <div>
    <p>{{ msg }}</p>
  </div>
</div>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

在這樣的程式碼中,我們完全不關心 msg 是怎麼被渲染到 p 標籤中的,我們所關心的只是:在 p 標籤中,渲染指定文字而已。

最後做一個總結,什麼叫做聲明式呢?

聲明式是:專注於結果 的一種程式設計範式,他 並不關心 完成一個功能的 詳細邏輯與步驟。 (注意:這並不意味著聲明式不需要過程!聲明式只是把過程進行了隱藏而已!)

04:命令式VS 聲明式

那麼在我們講解完成 命令式 和 聲明式 之後,很多同學肯定會對這兩種程式設計範式做一個對比。

是命令式好呢?還是聲明式好呢?

那麼想要弄清楚這個問題,那我們首先就需要先搞清楚,評價一種程式設計範式好還是不好的標準是什麼?

通常情況下,我們評估一個程式設計範式通常會從兩個面向著手:

  1. 效能
  2. 可維護性

那接下來我們就透過這兩個方面,來分析一下命令式和聲明式。

效能

效能一直是我們在進行專案開發時特別關注的方向,那麼我們通常如何表達一個功能的效能好壞呢?

我們來看一個例子:

為指定div 設定文字為“hello world”

那麼針對於這個需求而言,最簡單的程式碼就是:

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

你應該找不到比這個更簡單的程式碼實作了。

那麼此時我們把這個操作的 耗時 比喻成:1 。 (PS:耗時越少,效能越強)

然後我們來看聲明式,聲明式的程式碼為:

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

那麼:已知修改text最簡單的方式是innerText ,所以說無論聲明式的程式碼是如何實現的文字切換,那麼它的耗時一定是> 1 的,我們把它比作1 + n(對比的效能消耗)。

所以,由以上舉例可知:命令式的效能> 聲明式的效能

可維護性

可維護性代表的維度非常多,但是通常情況下,所謂的可維護性指的是:對程式碼可以方便的 閱讀、修改、刪除、增加 。

那麼想要達到這個目的,說白了就是:程式碼的邏輯要夠簡單,讓人一看就懂。

那麼明確了這個概念,我們來看下命令式和宣告式在同一段業務下的程式碼邏輯:

// 命令式
// 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
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
// 声明式
<div id="app">
  <div>
    <p>{{ msg }}</p>
  </div>
</div>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.

對於上述程式碼而言,聲明式 的程式碼明顯更有利於閱讀,所以也更有利於維護。

所以,由上述舉例可知:**命令式的可維護性< 聲明式的可維護性**

小結一下

由以上分析可知兩點內容:

  1. 命令式的效能> 聲明式的效能
  2. 命令式的可維護性< 聲明式的可維護性

那麼雙方各有優劣,我們在日常開發中該使用哪一種範式呢?

想要搞明白這一點,那我們還需要搞清楚更多的知識。

05:企業應用的開發與設計原則

企業應用的設計原則,想要描述起來比較複雜,為什麼?

因為對於 不同的企業類型(大廠、中小廠、人員外包、專案外包),不同的專案類型(前台、中台、後台)來說,對應的企業應用設計原則上可能會有一些差異。

所以我們在這裡所做的描述,會拋棄一些細微的差異,只抓住核心的重點來闡述。

無論什麼類型的企業,也無論它們在開發什麼類型的項目,那麼最關注的點無非就是兩個:

  1. 專案成本
  2. 開發體驗

專案成本

專案成本非常好理解,它決定了一個公司完成「這件事」所付出的代價,從而直接決定了這個專案是否是可以獲利的(大廠的燒錢專案例外)。

那麼既然專案成本如此重要,大家可以思考一下,決定專案成本的又是什麼?

沒錯!就是你的 開發週期。

開發週期越長,所付出的人員成本就會越高,導致專案成本變得越高。

透過我們前面的分析可知,聲明式的開發範式在 可維護性 上,是 大於 命令式的。

而可維護性從一定程度上就決定了,它會使專案的:開發週期變短、升級變得更容易 從而大量節約開發成本。

所以這也是為什麼 Vue 會變得越來越受歡迎的原因。

開發體驗

決定開發者開發體驗的核心要素,主要是在開發時和閱讀時的難度,這個被叫做:心智負擔。

心智負擔可以作為衡量開發難易度的一個標準,心智負擔高則證明開發的難度較高,心智負擔低則表示開發的難度較低,開發更加舒適。

那麼根據我們先前所說,聲明式的開發難度明顯低於命令式的開發難度。

所以對於開發體驗而言,聲明式的開發體驗較好,也就是 心智負擔較低。

06:為什麼說框架的設計過程其實是不斷取捨的過程?

Vue 作者尤雨溪在演講中說:框架的設計過程其實是不斷取捨的過程 。

這代表的是什麼意思呢?

想要搞明白這個,那麼再來明確一下之前說過的概念:

  1. 命令式的效能> 聲明式的效能
  2. 命令式的可維護性< 聲明式的可維護性
  3. 聲明式的框架本質上是由命令式的程式碼來去實現的
  4. 企業專案開發時,多使用聲明式框架

當我們明確好了這樣的問題之後,那麼我們接下來來思考一個問題:框架的發展與設計原則是什麼呢?

我們知道對於 Vue 而言,當我們使用它的是透過 聲明式 的方式進行使用,但是對於 Vue 內部而言,是透過 命令式 來進行的實作。

所以我們可以理解為:Vue 封裝了命令式的邏輯,而對外暴露出了聲明式的接口

那麼既然如此,我們明知 命令式的效能> 宣告式的效能 。那麼 Vue 為什麼還要選擇宣告式的方案呢?

其實原因非常的簡單,那就是因為:命令式的可維護性< 聲明式的可維護性 。

為指定的div 的子元素div 的子元素p 標籤,展示變數msg

以這個例子為例。

對於開發者而言,不需要專注於實現過程,只需要專注於最終的結果。

而對 Vue 而言,他所需要做的就是:封裝命令式邏輯,同時 盡可能的減少效能的損耗!它需要在 性能 與 可維護性 之間,找到一個平衡。從而找到一個 可維護性更好,性能相對更優 的一個點。

所以對於 Vue 而言,它的設計原則就是:在確保可維護性的基礎上,盡可能的減少效能的損耗。

那麼回到我們的標題:為什麼說框架的設計過程其實是不斷取捨的過程?

答案也就呼之欲出了,因為:

我們需要在可維護性和效能之間,找到一個平衡點。在保證可維護性的基礎上,盡可能的減少效能的損耗。

所以框架的設計過程其實是一個不斷在 可維護性和性能 之間進行取捨的過程

07:什麼是運行時?

在 Vue 3 的原始碼中存在一個runtime-core 的資料夾,該資料夾內存放的就是 執行時間 的核心程式碼邏輯。

runtime-core 中對外暴露了一個函數,叫做 渲染函數render

我們可以透過 render 取代 template 來完成 DOM 的渲染:

有些同學可能看不懂目前代碼是什麼意思,沒有關係,這不重要,後面我們會詳細去講。

<head>
  <meta charset="UTF-8">
  <title>Document</title>
  <script src="https://unpkg.com/vue@3.2.36/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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.

我們知道,在 Vue 的專案中,我們可以透過 tempalte 渲染 DOM 節點,如下:

<template>
	<div>hello render</div>
</template>
  • 1.
  • 2.
  • 3.

但對於 render 的例子而言,我們並沒有使用 tempalte,而是通過了一個名字叫做 render 的函數,回傳了一個不知道是什麼的東西,為什麼也可以渲染出 DOM 呢?

帶著這樣的問題,我們來看:

我們知道在上面的程式碼中,存在著一個核心函數:渲染函數 render,那麼這個 render 在這裡到底做了什麼事情呢?

我們透過一段程式碼實例來看下:

假設有一天你們領導跟你說:

我希望根據以下數據:

渲染出這樣一個div:

{
 type: 'div',
 props: {
  class: test
 },
 children: 'hello render'
}
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
<div>hello render</div>
  • 1.

那麼針對這樣的一個需求你會如何進行實現呢?大家可以在這裡先思考一下,試著進行實現,然後我們再繼續往下看..........

那麼接下來我們就根據這個需求來實現以下程式碼:

<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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.

在這樣的一個程式碼中,我們成功的透過一個 render 函數渲染出了對應的 DOM,和前面的 render 範例 類似,它們都是渲染了一個 vnode,你覺得這樣的程式碼真是妙極了!

但是你的領導花了一段時間你的 render 之後,卻說:天天這樣寫也太麻煩了,每次都得寫一個複雜的 vnode,能不能讓我直接寫 HTML 標籤結構的方式 你來進行渲染呢?

你想了想之後,說:如果是這樣的話,那就不是以上 運行時 的程式碼可以解決的了!

沒錯!我們剛剛所寫的這樣的一個“框架”,就是 運行時 的程式碼框架。

那麼最後,我們做一個總結:運行時可以利用render 把 vnode 渲染成真實 dom 節點。

08:什麼是編譯時?

在剛才,我們明確了,如果只靠 運行時,那麼是沒有辦法透過 HTML 標籤結構的方式 的方式來進行渲染解析的。

那麼想要實現這一點,我們就需要藉助另外一個東西,也就是 編譯時。

Vue 中的編譯時,更精確的說法應該是 編譯器 的意思。它的程式碼主要存在於 compiler-core 模組下。

我們來看如下程式碼:

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

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

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

<script>
  const { compile, createApp } = Vue

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

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

</html>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.
  • 20.
  • 21.
  • 22.
  • 23.
  • 24.
  • 25.
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
  • 33.

對於編譯器而言,它的主要作用是:把template 中的html 編譯成render 函式。然後再利用 運行時 透過 render 掛載對應的 DOM。

那最後,我們做一個總結:編譯時可以把html 的節點,編譯成 render函數

09:運行時+ 編譯時

前面兩小節我們已經分別了解了 運行時和編譯時,同時我們也知道了:vue 是一個運行時+編譯時 的框架!

vue 透過 compiler 解析 html 模板,產生 render 函數,然後透過 runtime 解析 render,從而掛載真實 dom。

那麼看到這裡可能有些同學就會有疑惑了,既然 compiler 可以直接解析html 模板,那為什麼還要產生 render 函數,然後再去渲染呢?為什麼不直接利用 compiler 來渲染呢?

即:為什麼vue 要設計成一個執行時期+編譯時的框架呢?

那麼想要理清楚這個問題,我們就需要知道 dom 渲染是如何進行的。

對於 dom 渲染而言,可以分成兩個部分:

  1. 初次渲染 ,我們可以把它叫做 掛載
  2. 更新渲染 ,我們可以把它叫做 打補丁

初次渲染

那什麼是初次渲染呢?

當初始 div 的 innerHTML 為空時,

<div id="app"></div>
  • 1.

我們在該 div 中渲染如下節點:

<ul>
 <li>1</li>
 <li>2</li>
 <li>3</li>
</ul>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

那麼這樣的一次渲染,就是 初始渲染。在這樣的一次渲染中,我們會產生一個 ul 標籤,同時產生三個 li 標籤,並且把他們掛載到 div 中。

更新渲染

那麼此時如果 ul 標籤的內容改變了:

<ul>
 <li>3</li>
 <li>1</li>
 <li>2</li>
</ul>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.

li - 3 上升到了第一位,那麼此時大家可以想一下:我們期望瀏覽器如何來更新這次渲染呢?

瀏覽器更新這次渲染無非有兩種方式:

  1. 刪除原有的所有節點,重新渲染新的節點
  2. 刪除原位置的 li - 3,在新位置插入 li - 3

那麼大家覺得這兩種方式哪一種方式比較好呢?那我們來分析一下:

  1. 首先對於第一種方式而言:它的好處在於不需要進行任何的比對,需要執行6 次(刪除3 次,重新渲染3 次)dom 處理即可。
  2. 對於第二種方式而言:在邏輯上相對比較複雜。他需要分兩步來做:
  1. 對比 舊節點 和 新節點 的差異
  2. 根據差異,刪除一個 舊節點,增加一個 新節點

那麼根據以上分析,我們知道了:

  1. 第一種方式:會涉及到更多的 dom 操作
  2. 第二種方式:會涉及到 js 計算+ 少量的 dom 操作

那麼這兩種方式,哪一種比較快呢?我們來實驗一下:

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')
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.
  • 19.

從結果可以看出,dom 的操作比 js 的操作耗時多得多,即:dom** 操作比 js 更耗費效能**。

那麼根據這樣的一個結論,回到我們剛才所說的場景:

  1. 首先對於第一種方式而言:它的好處在於不需要進行任何的比對,只需要執行6 次(刪除3 次,重新渲染3 次)dom 處理即可。
  2. 對於第二種方式而言:在邏輯上相對比較複雜。他需要分兩步來做:

對比 舊節點 和 新節點 的差異

根據差異,刪除一個 舊節點,增加一個 新節點

根據結論可知:方式一會比方式二更消耗性能(即:性能更差)。

那麼得出這樣的結論之後,我們回過頭去再來看最初的問題:為什麼vue 要設計成一個運行時+編譯時的框架呢?

答:

  1. 針對於 純運行時 而言:因為不存在編譯器,所以我們只能夠提供一個複雜的 JS 物件。
  2. 針對於 純編譯時 而言:因為缺少執行時,所以它只能把分析差異的操作,放到 編譯時 進行,同樣因為省略了運行時,所以速度可能會更快。但這種方式這將損失彈性(具體可查看第六章虛擬 DOM ,或可點擊這裡查看官方範例)。例如svelte ,它就是一個純編譯時的框架,但是它的實際運作速度可能達不到理論上的速度。
  3. 執行時期+ 編譯時:例如 vue 或 react 都是透過這種方式來進行建構的,使其可以在保持靈活性的基礎上,盡量的進行效能的最佳化,從而達到一種平衡。

10:什麼是副作用

在 vue 的原始碼中,會大量的涉及到一個概念,那就 副作用。

所以我們要先了解副作用代表的是什麼意思。

副作用指的是:當我們 對資料進行 setter 或 getter 操作時,所產生的一系列後果。

那麼具體是什麼意思呢?我們分別來說一下:

setter 所表示的是 賦值 操作,比如說,當我們執行以下程式碼時:

msg = '你好,世界'
  • 1.

這時 msg 觸發了一次 setter 的行為。

那麼假如說,msg 是一個響應性數據,那麼這樣的一次數據改變,就會影響到對應的視圖改變。

那麼我們就可以說:msg 的 setter 行為,觸發了一次副作用,導致視圖跟隨發生了變化。

吸氣劑

getter 所表示的是 取值 操作,比如說,當我們執行以下程式碼時:

element.innerText = msg
  • 1.

此時對於變數 msg 而言,就觸發了一次 getter 操作,那麼這樣的一次取值操作,同樣會導致 element 的 innerText 改變。

所以我們可以說:msg 的 getter 行為觸發了一次副作用,導致 element 的 innterText 改變了。

副作用會有多個嗎?

那麼明確好了副作用的基本概念之後,那麼大家想一想:副作用可能會有多個嗎?

答案是:可以的。

舉個簡單的例子:

<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>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.

在這樣的一個程式碼中 obj.value 觸發了一次 setter 行為,但是會導致兩個 p 標籤的內容改變,也就是產生了兩次副作用。

小節一下

根據本小節我們知道了:

  1. 副作用指的是:對資料進行 setter 或 getter 操作時,所產生的一系列後果
  2. 副作用可能是會有多個的。

11:Vue 3 框架設計概述

根據前面的學習我們已經知道了:

  1. 什麼是聲明式
  2. 什麼是命令式
  3. 什麼是運行時
  4. 什麼是編譯時
  5. 什麼是運行時+編譯時
  6. 同時也知道了框架的設計過程本身就是一個不斷取捨的過程

那麼就了解了這些內容之後,下來 vue3 的一個基本架構設計:

對 vue3 而言,核心大致可分為三大模組:

  1. 回應性:reactivity
  2. 運行時:runtime
  3. 編譯器:compiler

我們以以下基本結構來描述三者之間的基本關係:

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

<script>
import { reactive } from 'vue'
export default {
	setup() {
		const target = {
			name: '张三'
		}
		const proxyTarget = reactive(target)
		return {
			proxyTarget
		}
	}
}
</script>
  • 1.
  • 2.
  • 3.
  • 4.
  • 5.
  • 6.
  • 7.
  • 8.
  • 9.
  • 10.
  • 11.
  • 12.
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
  • 18.

在以上程式碼中:

  1. 首先,我們透過 reactive 方法,聲明了一個響應式資料。

該方法是 reactivity 模組對外暴露的一個方法

可以接收一個複雜資料類型,作為 Proxy (現在很多同學可能還不了解什麼是 proxy ,沒有關係後面我們會詳細介紹它,現在只需要有個印象即可)的 被代理對象(target)

傳回一個Proxy 類型的 代理物件(proxyTarget)

當 proxyTarget 觸發 setter 或 getter 行為時,會產生對應的副作用

  1. 然後,我們在 tempalte 標籤中,寫入了一個 div。我們知道這裡所寫入的 html 並不是真實的 html,我們可以把它叫做 模板,該模板的內容會被 編譯器( compiler ) 進行編譯,從而產生一個 render 函數
  2. 最後,vue 會利用 執行時間(runtime) 來執行 render 函數,從而渲染出真實 dom

以上就是 reactivity、runtime、compiler 三者之間的運行關係。