手寫 Vue 系列 之 從 Vue1 升級到 Vue2

李永寧發表於2022-03-15

前言

上一篇文章 手寫 Vue 系列 之 Vue1.x 帶大家從零開始實現了 Vue1 的核心原理,包括如下功能:

  • 資料響應式攔截

    • 普通物件

    • 陣列

  • 資料響應式更新

    • 依賴收集

      • Dep

      • Watcher

    • 編譯器

      • 文字節點

      • v-on:click

      • v-bind

      • v-model

在最後也詳細講解了 Vue1 的誕生以及存在的問題:Vue1.x 在中小型系統中效能會很好,定向更新 DOM 節點,但是大型系統由於 Watcher 太多,導致資源佔用過多,效能下降。於是 Vue2 中通過引入 VNode 和 Diff 的來解決這個問題,

所以接下來的系列內容就是升級上一篇文章編寫的 lyn-vue 框架,將它從 Vue1 升級到 Vue2。所以建議整個系列大家按順序去閱讀學習,如若強行閱讀,可能會產生雲裡霧裡的感覺,事倍功半。

另外歡迎 關注 以防迷路,同時系列文章都會收錄到 精通 Vue 技術棧的原始碼原理 專欄,也歡迎關注該專欄。

目標

升級後的框架需要將如下示例程式碼跑起來

示例

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

<head>
  <meta charset="UTF-8">
  <title>Lyn Vue2.0</title>
</head>

<body>
  <div id="app">
    <h3>資料響應式更新 原理</h3>
    <div>{{ t }}</div>
    <div>{{ t1 }}</div>
    <div>{{ arr }}</div>
    <h3>methods + computed + 非同步更新佇列 原理</h3>
    <div>
      <p>{{ counter }}</p>
      <div>{{ doubleCounter }}</div>
      <div>{{ doubleCounter }}</div>
      <div>{{ doubleCounter }}</div>
      <button v-on:click="handleAdd"> Add </button>
      <button v-on:click="handleMinus"> Minus </button>
    </div>
    <h3>v-bind</h3>
    <span v-bind:title="title">右鍵審查元素檢視我的 title 屬性</span>
    <h3>v-model 原理</h3>
    <div>
      <input type="text" v-model="inputVal" />
      <div>{{ inputVal }}</div>
    </div>
    <div>
      <input type="checkbox" v-model="isChecked" />
      <div>{{ isChecked }}</div>
    </div>
    <div>
      <select v-model="selectValue">
        <option value="1">1</option>
        <option value="2">2</option>
        <option value="3">3</option>
      </select>
      <div>{{ selectValue }}</div>
    </div>
    <h3>元件 原理</h3>
    <comp></comp>
    <h3>插槽 原理</h3>
    <scope-slot></scope-slot>
    <scope-slot>
      <template v-slot:default="scopeSlot">
        <div>{{ scopeSlot }}</div>
      </template>
    </scope-slot>
  </div>
  <script type="module">
    import Vue from './src/index.js'
    const ins = new Vue({
      el: '#app',
      data() {
        return {
          // 原始值和物件的響應式原理
          t: 't value',
          t1: {
            tt1: 'tt1 value'
          },
          // 陣列的響應式原理
          arr: [1, 2, 3],
          // 響應式更新
          counter: 0,
          // v-bind
          title: "I am title",
          // v-model
          inputVal: 'test',
          isChecked: true,
          selectValue: 2,
        }
      },
      // methods + 事件 + 資料響應式更新 原理
      methods: {
        handleAdd() {
          this.counter++
        },
        handleMinus() {
          this.counter--
        }
      },
      // computed + 非同步更新佇列 的原理
      computed: {
        doubleCounter() {
          console.log('evalute doubleCounter')
          return this.counter * 2
        }
      },
      // 元件
      components: {
        // 子元件
        'comp': {
          template: `
            <div>
              <p>{{ compCounter }}</p>
              <p>{{ doubleCompCounter }}</p>
              <p>{{ doubleCompCounter }}</p>
              <p>{{ doubleCompCounter }}</p>
              <button v-on:click="handleCompAdd"> comp add </button>
              <button v-on:click="handleCompMinus"> comp minus </button>
            </div>`,
          data() {
            return {
              compCounter: 0
            }
          },
          methods: {
            handleCompAdd() {
              this.compCounter++
            },
            handleCompMinus() {
              this.compCounter--
            }
          },
          computed: {
            doubleCompCounter() {
              console.log('evalute doubleCompCounter')
              return this.compCounter * 2
            }
          }
        },
        // 插槽
        'scope-slot': {
          template: `
            <div>
              <slot name="default" v-bind:slotKey="slotKey">{{ slotKey }}</slot>
            </div>
          `,
          data() {
            return {
              slotKey: 'scope slot content'
            }
          }
        }
      }
    })
    // 資料響應式攔截
    setTimeout(() => {
      console.log('********** 屬性值為原始值時的 getter、setter ************')
      console.log(ins.t)
      ins.t = 'change t value'
      console.log(ins.t)
    }, 1000)

    setTimeout(() => {
      console.log('********** 屬性的新值為物件的情況 ************')
      ins.t = {
        tt: 'tt value'
      }
      console.log(ins.t.tt)
    }, 2000)

    setTimeout(() => {
      console.log('********** 驗證對深層屬性的 getter、setter 攔截 ************')
      ins.t1.tt1 = 'change tt1 value'
      console.log(ins.t1.tt1)
    }, 3000)

    setTimeout(() => {
      console.log('********** 將值為物件的屬性更新為原始值 ************')
      console.log(ins.t1)
      ins.t1 = 't1 value'
      console.log(ins.t1)
    }, 4000)

    setTimeout(() => {
      console.log('********** 陣列操作方法的攔截 ************')
      console.log(ins.arr)
      ins.arr.push(4)
      console.log(ins.arr)
    }, 5000)
  </script>
</body>

</html>

知識點

示例程式碼涉及的知識點包括:

  • 基於模版解析的編譯器

    • 解析模版得到 AST

    • 基於 AST 生成渲染函式

    • render helper

      • _c,建立指定標籤的 VNode

      • _v,建立文字節點的 VNode

      • _t,建立插槽節點的 VNode

    • VNode

  • patch

    • 原生標籤和元件的初始渲染

      • v-model

      • v-bind

      • v-on

    • diff

  • 插槽原理

  • computed

  • 非同步更新佇列

效果

示例程式碼最終的執行效果如下:

動圖地址: https://gitee.com/liyongning/typora-image-bed/raw/master/202203092034307.image

說明

該框架只為講解 Vue 的核心原理,沒有什麼健壯性可言,說不定你換個示例程式碼可能就會報錯、跑不起來,但是用來學習是完全足夠了,基本上把 Vue 的核心原理(知識點)都實現了一遍。

所以接下來就開始正式的學習之旅吧,加油!!

連結

感謝各位的:關注點贊收藏評論,我們下期見。


當學習成為了習慣,知識也就變成了常識。 感謝各位的 關注點贊收藏評論

新視訊和文章會第一時間在微信公眾號傳送,歡迎關注:李永寧lyn

文章已收錄到 github 倉庫 liyongning/blog,歡迎 Watch 和 Star。

相關文章