vue3原始碼剖析之簡單實現方法
目錄
- 前言
- 🍹準備工作
- 🍲3用法
- 🍖實現
- 總結
前言
最近,由於我的第一個vue3 + ts的正式專案,已經進入驗收階段。聽你們老說vue3、vue3的,我就想著去看看vue3到底和vue2有啥區別。🤷🏻♀️🤷🏻♀️🤷🏻♀️
文章主要闡述vue3的API用法,以及簡單地實現一個vue3。帶大家感受一下vue3與之前vue2的區別。以及簡單帶大家揭祕原始碼中vue3初始化的一個流程。
🍹準備工作
要想看看vue3內部的原始碼是咋搞得,首先跟vue2原始碼剖析一樣,先從上下一份原始碼到本地。
接著就是安裝依賴:
yarn --ignore-scripts
在你執行命令的時候可能會遇到node版本過低的錯誤:
解決此問題可以升級自己的node版本,或者忽略該engine。
如果選擇忽略的話可以設定
yarn config set --ignore-engines true
然後執行依賴安裝。
依賴安裝完成後,編譯打包生成vue檔案:
yarn dev
http://www.cppcns.com
需要除錯的話,可以在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,newVal);
}
})
// 執行render thiswww.cppcns.com.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中的。在渲染的時候,直接將整合的變數集傳www.cppcns.com入即可。當然proxy中也會存在set方法,需要先代理,然後在外部獲取變數做值得修改才能觸發,在此有興趣的同學可以自行研究哦!
總結
到此這篇關於vue3原始碼剖析之簡單實現的文章就介紹到這了,更多相關vue3原始碼剖析內容請搜尋我們以前的文章或繼續瀏覽下面的相關文章希望大家以後多多支援我們!