vue3源碼剖析之簡單實現方法

前言

最近,由於我的第一個vue3 + ts的正式項目,已經進入驗收階段。聽你們老說vue3、vue3的,我就想著去看看vue3到底和vue2有啥區別。🤷🏻‍♀️🤷🏻‍♀️🤷🏻‍♀️

文章主要闡述vue3的API用法,以及簡單地實現一個vue3。帶大傢感受一下vue3與之前vue2的區別。以及簡單帶大傢揭秘源碼中vue3初始化的一個流程。

🍹準備工作

要想看看vue3內部的源碼是咋搞得,首先跟vue2源碼剖析一樣,先從github上下一份源碼到本地。

接著就是安裝依賴:

yarn --ignore-scripts

在你執行命令的時候可能會遇到node版本過低的錯誤:

解決此問題可以升級自己的node版本,或者忽略該engine。

如果選擇忽略的話可以設置

yarn config set --ignore-engines true

然後執行依賴安裝。

依賴安裝完成後,編譯打包生成vuejs文件:

yarn dev

需要調試的話,可以在packages\vue\examples文件下建立測試文件。引用打包後的vue文件,可以應用packages\vue\dist\vue.global.js。

🍲vue3用法

vue3的特性我就不多闡述瞭,就vue3的用法而言,更傾向於函數式編程,通過對外暴露Vue中的createApp()API,以工廠函數的方式創建瞭一個應用程序實例。相比較vue2的new Vue實例,更加貼切。

在源碼文件中,我們新建一個init.html文件。

<script src="../dist/vue.global.js"></script>
 <body>
  <div id="app">{{name}}</div>
  <script>
    const { createApp } = Vue
    const app1 = createApp({
      data() {
        return {
          name: 'clying'
        }
      },
      setup() {
        return {
          name: 'deng'
        }
      }
    }).mount('#app')
  </script>
</body>

根據上例,我們可以看出vue3是即支持Composition API,也支持Options API,兩者可以同時使用。

但是,我們可以看到在data和setup中,我同時使用瞭一個name變量進行賦值。那麼頁面中會展示哪一個呢?

3!2!1!上答案:

可以明顯看出composition-api中setup優先級更高。

當然也可以在源碼中的packages\runtime-core\src\componentPublicInstance.ts看到,通過switch先判斷setup中的變量是否存在,然後再去判斷data中的變量。所以setup中變量的優先級會高於data中的變量。

🍖實現

通過上面的用法,我們可以知道vue3中會對外暴露一個Vue變量,內部存在createApp、reactive等方法。

在此,我們先實現vue3的初始化框架。就createApp而言,它會接收用戶傳入的參數:data()、setup()等,最後進行實例掛載mount。所以在createApp中會接收一些參數options、內部還會存在mount方法。

const Vue = {
    createApp(options) {
      return {
        mount(selector) { //解析、獲取render、掛載
        }
      }
    }
}

在mount中通過selector獲取到宿主元素。

接下來就是對模板的編譯,由於將template編譯AST後,依舊要轉成render函數。在此我們簡化操作,在編譯時直接返回一個render。

mount(selector) { //解析、獲取render、掛載
  const parent = document.querySelector(selector)
  console.log(parent);
  if (!options.render) {
    // 編譯返回render
    options.render = this.compileToFunction(parent.innerHTML)
  }
},
compileToFunction(template) {
  return function render() {
    const h = document.createElement('div')
    h.textContent = this.name
    return h
  }
}

拿到render之後,執行它,將其添加到宿主元素中,將老的節點刪除。

在執行render的時候,我們需要註意它的this指向。如果給它綁定data,那麼它展示的就會使data中的name。

mount(selector) { //解析、獲取render、掛載
  const parent = document.querySelector(selector)
  console.log(parent);
  if (!options.render) {
    // 編譯返回render
    options.render = this.compileToFunction(parent.innerHTML)
  }
  // 執行render 
  const el = options.render.call(options.data())
  parent.innerHTML = ''
  parent.appendChild(el)
},

可以看到頁面上展示的是clying。反之,如果綁定的是options.setup(),那麼頁面上出現的就是deng。

對於vue3的用法,我們知道setup的優先級是高於data的。那我們可以使用代理啊,將兩者的屬性變量,通過代理的方式,糅合到一起,優先考慮setup。當訪問相同name時,實際訪問的就是setup中的name。

mount(selector) { //解析、獲取render、掛載
  const parent = document.querySelector(selector)
  console.log(parent);
  if (!options.render) {
    // 編譯返回render
    options.render = this.compileToFunction(parent.innerHTML)
  }
  if (options.setup) {
    this.setupState = options.setup()
  }
  if (options.data) {
    this.data = options.data()
  }
  this.proxy = new Proxy(this, {
    get(target, key) {
      if (key in target.setupState) {
        return target.setupState[key]
      } else if (key in target.data) {
        return target.data[key]
      }// 還可能存在props、watch等其他同名變量
    }, 
    set(target, key, value, newVal) {
      console.log(target, key, value, newVal);
    }
  })
  // 執行render  this.proxy就是整合setup和data的上下文
  const el = options.render.call(this.proxy)
  console.log(el, options.render);
  parent.innerHTML = ''
  parent.appendChild(el)
},

在proxy的get中,先看setup中是否存在目標屬性,如果存在的話返回的就是setup中的屬性變量,否則就是data中的。在渲染的時候,直接將整合的變量集傳入即可。當然proxy中也會存在set方法,需要先代理,然後在外部獲取變量做值得修改才能觸發,在此有興趣的同學可以自行研究哦!

總結

到此這篇關於vue3源碼剖析之簡單實現的文章就介紹到這瞭,更多相關vue3源碼剖析內容請搜索WalkonNet以前的文章或繼續瀏覽下面的相關文章希望大傢以後多多支持WalkonNet!

推薦閱讀: