vue3 出來有一段時間了。今天正式開始記錄一下
vue 3.0.0-beta
源碼學習心得。
本文編寫於 2020-06-10
,腳手架使用 vite-app
版本 0.20.0
,內置 vue 3.0.0-beta.14
。javascript
ps: 可能大部分人都不清楚 vue3
的開發api,將源碼以前先講述 使用方法css
最容易搭建 vue3
的方式就是使用做者的 vitehtml
經過 npm
安裝前端
$ npm init vite-app <project-name> $ cd <project-name> $ npm install $ npm run dev
也能夠經過 yarn
安裝vue
$ yarn create vite-app <project-name> $ cd <project-name> $ yarn $ yarn dev
安裝的過程當中你可能遇到如下問題(反正本菜遇到了)java
No valid exports main found for' C:\xxx\xxx\node_ modules\@rollup\pluginutils'
The engine "node" is incompatible with this module. Expected version ">= 10.16.0". Got "10.15.3
異常1:本菜翻閱了 vite
的 issue,而後 google + baidu 一無所得, 最後發現是由於本菜 node
版本爲 13.5.0
致使的(版本太高),node
異常2:很明顯啦,node
版本過低了。react
最後的解決方式是:本菜經過 nvm
將 node 版本切換到 12.12.0
,至於 nvm
沒使用過的童鞋們能夠去嘗試下哦。特別好用webpack
當瀏覽器識別 type="module"
引入js文件的時候,內部的 import 就會發起一個網絡請求,嘗試去獲取這個文件。git
那麼就能夠經過經過攔截路由 /
和 .js
結尾的請求。而後經過 node 去加載對應的 .js
文件
const fs = require('fs') const path = require('path') const Koa = require('koa') const app = new Koa() app.use(async ctx=>{ const {request:{url} } = ctx // 首頁 if(url=='/'){n ctx.type="text/html" ctx.body = fs.readFileSync('./index.html','utf-8') }else if(url.endsWith('.js')){ // js文件 const p = path.resolve(__dirname,url.slice(1)) ctx.type = 'application/javascript' const content = fs.readFileSync(p,'utf-8') ctx.body = content } }) app.listen(3001, ()=>{ console.log('聽我口令,3001端口,起~~') })
若是隻是簡單的代碼,這樣加載就能夠了。徹底是按需加載,比起 webpack 的語法解析性能固然會快很是多。
可是遇到第三方庫以上代碼就會找不到 .js
文件的位置了,此時 vite
會用 es-module-lexer
把文件解析成 ast
,拿到 import
的地址。
經過分析 import
的內容,識別是否是第三方庫(這個主要是看前面是否是相對路徑)
若是是第三方庫就去 node_modules
中查找,vite
中經過在第三方庫中添加前綴 /@modules/
,而後發現了 /@modules/
後走 第三方庫邏輯
if(url.startsWith('/@modules/')){ // 這是一個node_module裏的東西 const prefix = path.resolve(__dirname,'node_modules',url.replace('/@modules/','')) const module = require(prefix+'/package.json').module const p = path.resolve(prefix,module) const ret = fs.readFileSync(p,'utf-8') ctx.type = 'application/javascript' ctx.body = rewriteImport(ret) }
這樣第三方庫也能夠解析了。而後是 .vue
單文件解析。
首先 xx.vue
返回的格式大概是這樣的
const __script = { setup() { ... } } import {render as __render} from "/src/App.vue?type=template&t=1592389791757" __script.render = __render export default __script
而後能夠用 @vue/compiler-dom
把 html
解析成 render
解析 .css
就更加簡單了。經過 document.createElement('style')
而後再注入就行了
ps:具體的源碼還沒看(先搞點 Vue3 吧)
正式進入正題。
做爲 vue2
的使用者最想知道的確定是 vue3
的數據劫持和雙向綁定了。在 vue3中,雙向綁定和可選項,若是須要使用雙向綁定的須要經過 reactive
方法進行數據劫持。
在這以前呢還須要知道一個函數 setup
setup
是使用 Composition API
的入口setup
能夠返回一個對象,該對象的屬性會被合併到渲染上下文,並能夠在模板中直接使用setup
也能夠返回 render
函數如今開始寫一個簡單的 vue
<template> <div> <div>{{ count }}</div> <button @click="increment">count++</button> </div> </template> <script> import { reactive } from 'vue' export default { setup() { let count = reactive({ num: 0 }) const increment = () => count.num++ return { count, increment } } } </script>
emmm。這樣點擊按鈕就能夠動態改變 dom 中的 count 值了。
如今開始解讀 reactive
源碼。
首先找到 reactivity.esm-browser.js
文件,找到 626
行。
function reactive(target) { // if trying to observe a readonly proxy, return the readonly version. if (target && target.__v_isReadonly) { return target; } return createReactiveObject(target, false, mutableHandlers, mutableCollectionHandlers); }
上面的 __v_isReadonly
實際上是一個 typescript 的枚舉值
export const enum ReactiveFlags { skip = '__v_skip', isReactive = '__v_isReactive', isReadonly = '__v_isReadonly', raw = '__v_raw', reactive = '__v_reactive', readonly = '__v_readonly' }
不一樣的枚舉值對應了不一樣的數據劫持方式,例如 reactive、 shallowReactive 、readonly、 shallowReadonly
而後進入 createReactiveObject
在 649
行,意思就是:建立響應式對象
function createReactiveObject(target, isReadonly, baseHandlers, collectionHandlers) { // 略... // 若是target已經代理了, 返回target if (target.__v_raw && !(isReadonly && target.__v_isReactive)) { return target; } // target already has corresponding Proxy if (hasOwn(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */)) { return isReadonly ? target.__v_readonly : target.__v_reactive; } if (!canObserve(target)) { return target; } // 重點... // collectionHandlers:對引用類型的劫持, // baseHandlers: 對進行基本類型的劫持 const observed = new Proxy(target, collectionTypes.has(target.constructor) ? collectionHandlers : baseHandlers); def(target, isReadonly ? "__v_readonly" /* readonly */ : "__v_reactive" /* reactive */, observed); return observed; }
createReactiveObject
作了如下幾件事
collectionHandlers
或 baseHandlers
)實現劫持的主要方法是經過 Proxy
方法,(Proxy 使用能夠看看阮老師的博客),順騰摸瓜找到 mutableHandlers
定義的地方。在 338
行
const mutableHandlers = { get, set, deleteProperty, has, ownKeys }; // 229行 const get = /*#__PURE__*/ createGetter(); // 251 行 function createGetter(isReadonly = false, shallow = false) { return function get(target, key, receiver) { // 一些 __v_isReactive、__v_isReadonly、__v_raw的處理 // 略... // 數組操做 const targetIsArray = isArray(target); if (targetIsArray && hasOwn(arrayInstrumentations, key)) { return Reflect.get(arrayInstrumentations, key, receiver); } // 非數組 const res = Reflect.get(target, key, receiver); // 其餘 調用 track 返回 res 的狀況 // 略... // 若是可寫,那麼會調用 track !isReadonly && track(target, "get" /* GET */, key); // 若是是對象呢。那麼遞歸 return isObject(res) ? isReadonly ? // need to lazy access readonly and reactive here to avoid // circular dependency readonly(res) : reactive(res) : res; }; }
mutableHandlers
主要是一個含有 Proxy
各類方法的常量。
get 指向了方法 createGetter
, 建立 get 劫持
createGetter
主要作了如下事情
hasOwn(arrayInstrumentations, key)
則調用 arrayInstrumentations
獲取值track
reactive
那麼數組的 arrayInstrumentations
是什麼呢? 咱們來到源碼的 第 234
行。
const arrayInstrumentations = {}; ['includes', 'indexOf', 'lastIndexOf'].forEach(key => { arrayInstrumentations[key] = function (...args) { // const arr = toRaw(this); for (let i = 0, l = this.length; i < l; i++) { track(arr, "get" /* GET */, i + ''); } // we run the method using the original args first (which may be reactive) // 咱們首先 以原始args 運行該方法(多是反應性的) const res = arr[key](...args); if (res === -1 || res === false) { // if that didn't work, run it again using raw values. // 若是那不起做用,則使用原始值再次運行它。 return arr[key](...args.map(toRaw)); } else { return res; } }; });
經過 arrayInstrumentations
獲得 hasOwn(arrayInstrumentations, key)
就是指 ['includes', 'indexOf', 'lastIndexOf']
arrayInstrumentations
中仍是調用了 track
方法,那麼 track
方法就更加神祕了。來看看它的源碼吧? 源碼在 126
行
function track(target, type, key) { if (!shouldTrack || activeEffect === undefined) { return; } let depsMap = targetMap.get(target); if (!depsMap) { targetMap.set(target, (depsMap = new Map())); } let dep = depsMap.get(key); if (!dep) { depsMap.set(key, (dep = new Set())); } if (!dep.has(activeEffect)) { dep.add(activeEffect); activeEffect.deps.push(dep); if ( activeEffect.options.onTrack) { activeEffect.options.onTrack({ effect: activeEffect, target, type, key }); } } }
首先 track
須要 shouldTrack
和 activeEffect
爲真。
在不考慮 activeEffect
的狀況下。track
所作的事情就是
map
activeEffect
塞到 map
中onTrack
而後 activeEffect
又是什麼呢?找到 3687
行,這裏有個 createReactiveEffect
函數。
function createReactiveEffect(fn, options) { const effect = function reactiveEffect(...args) { return run(effect, fn, args); }; effect._isEffect = true; effect.active = true; effect.raw = fn; effect.deps = []; effect.options = options; return effect; }
createReactiveEffect
是在 effect
中被調用的
而 effect
分別在如下地方被使用了
trigger
經過 scheduleRun
調用 effect
:源碼 3756
行mountComponent
經過 setupRenderEffect
調用 effect
:源碼 6235 行
createComponentInstance
以後doWatch
經過 scheduler
調用 effect
先開始講述 trigget
相關的代碼(核心哦)
function trigger(target, type, key, extraInfo) { const depsMap = targetMap.get(target); // 略... const effects = new Set(); const computedRunners = new Set(); if (type === "clear" /* CLEAR */) { // collection being cleared, trigger all effects for target depsMap.forEach(dep => { addRunners(effects, computedRunners, dep); }); } // 略... const run = (effect) => { scheduleRun(effect, target, type, key, extraInfo); }; computedRunners.forEach(run); effects.forEach(run); }
trigger
最終是在 set
函數中被使用,源碼 3855
行,這個 set
就是數據劫持所用的 set
function set(target, key, value, receiver) { value = toRaw(value); const oldValue = target[key]; if (isRef(oldValue) && !isRef(value)) { oldValue.value = value; return true; } const hadKey = hasOwn(target, key); const result = Reflect.set(target, key, value, receiver); { const extraInfo = { oldValue, newValue: value }; if (!hadKey) { trigger(target, "add" /* ADD */, key, extraInfo); } else if (hasChanged(value, oldValue)) { trigger(target, "set" /* SET */, key, extraInfo); } } } return result; }
在源碼 3900
行中,被 mutableHandlers
、readonlyHandlers
等函數中被使用。
還記得嗎? mutableHandlers
是什麼? 能夠回到文章開頭部分 reactive
源碼講解之初的 createReactiveObject
方法。在經過 Proxy
劫持數據的時候用的就是 mutableHandlers
因此,這裏就成環了。
effect
纔是響應式的核心,在 mountComponent
、doWatch
、reactive
中被調用。reactive
中 經過 Proxy
實現劫持。Proxy
劫持set
時調用 trigger
。targger
中清除收集並觸發目標的全部 effects
patch
遊戲結束。全棧
或 Vue
有好禮相送哦