只有刻意練習才能提升。javascript
前面關注Vue3主要是閱讀源碼也趁機摸魚了提了一些PR,竟然還有一個經過的算是給vue大業也作了點點貢獻。html
https://github.com/vuejs/vue-next/pull/1389vue
爲了更好的理解Vue3源碼我計劃使用漸進式的方法完成一個簡寫版的Vue框架。java
歡迎你們持續關注、首先作一個簡單的計劃。git
這個計劃必定會變😜,要否則怎麼叫迭代呢。github
📎 Mock狀態web
🚀簡版實現數組
Step | 00 | 01 | 02 | 03 | 04 | 05 | 06 | |
---|---|---|---|---|---|---|---|---|
響應式邏輯 | - | 📎 | 🚀 | 🚀 | 🚀 | 🚀 | 🚀 | |
編譯函數 | - | 📎 | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
Parser | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | ||
Transformer | - | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
Generator | - | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
運行時環境 | - | 📎 | 📎 | 🚀 | 🚀 | 🚀 | 🚀 | |
渲染器 | - | 📎 | 📎 | 📎 | 🚀 | 🚀 | 🚀 | |
核心特性 | Dom Diff | - | - | - | - | - | 🚀 | 🚀 |
靜態提高 | - | - | - | - | - | - | 🚀 | |
自定義渲染器 | - | - | - | - | - | - | 🚀 | |
- | - | - | - | - | - | 🚀 |
完整的代碼瀏覽器
想象一下若是沒有MVVM框架咱們要怎麼實現一個這樣的功能。性能優化
const data = {
message: 'Hello Vue 3!!' } 複製代碼
<div id='app'>
<input /> <button></button> </div> 複製代碼
function update() {
// 更新視圖 document.querySelector('button').innerHTML = data.message document.querySelector('input').value = data.message } 複製代碼
// 首次數據渲染
update() 複製代碼
document.querySelector('button').addEventListener('click', function () {
data.message = data.message.split('').reverse().join('') update() }) 複製代碼
document.querySelector('input').addEventListener('keyup', function () {
data.message = this.value update() }) 複製代碼
MVVM框架其實就是在原先的View和Model之間增長了一個VM層完成如下工做。完成數據與視圖的監聽。咱們這一步先寫一個Mock版本。其實就是先針對固定的視圖和數據模型實現監聽。
咱們MVVM的框架接口和Vue3如出一轍。
初始化須要肯定
const App = {
// 視圖模板 template: ` <input v-model="message"/> <button @click='click'>{{message}}</button> `, // 數據模型 data() { return { message: 'Hello Vue 3!!' } }, // 行爲函數 methods: { click() { this.message = this.message.split('').reverse().join('') } } } const { createApp } = Vue createApp(App).mount('#app') 複製代碼
const Vue = {
createApp(config) { // 編譯過程 const compile = (template) => (observed, dom) => { } // 生成渲染函數 const render = compile() // 定義響應函數 let effective // 數據劫持 observed = new Proxy(config.data(), { }) return { // 初始化 mount: function (container) { } } } } 複製代碼
MVVM框架中的渲染函數是會經過視圖模板的編譯創建的。
// 編譯函數
// 輸入值爲視圖模板 const compile = (template) => { //渲染函數 return (observed, dom) => { // 渲染過程 } } 複製代碼
簡單的說就是對視圖模板進行解析並生成渲染函數。
大概要處理如下三件事
肯定哪些值須要根據數據模型渲染
// <button>{{message}}</button>
// 將數據渲染到視圖 button = document.createElement('button') button.innerText = observed.message dom.appendChild(button) 複製代碼
綁定模型事件
// <button @click='click'>{{message}}</button>
// 綁定模型事件 button.addEventListener('click', () => { return config.methods.click.apply(observed) }) 複製代碼
肯定哪些輸入項須要雙向綁定
// <input v-model="message"/>
// 建立keyup事件監聽輸入項修改 input.addEventListener('keyup', function () { observed.message = this.value }) 複製代碼
完整的代碼
const compile = (template) => (observed, dom) => {
// 從新渲染 let input = dom.querySelector('input') if (!input) { input = document.createElement('input') input.setAttribute('value', observed.message) input.addEventListener('keyup', function () { observed.message = this.value }) dom.appendChild(input) } let button = dom.querySelector('button') if (!button) { console.log('create button') button = document.createElement('button') button.addEventListener('click', () => { return config.methods.click.apply(observed) }) dom.appendChild(button) } button.innerText = observed.message } 複製代碼
Vue廣泛走的就是數據劫持方式。不一樣的在於使用DefineProperty仍是Proxy。也就是一次一個屬性劫持仍是一次劫持一個對象。固然後者比前者聽着就明顯有優點。這也就是Vue3的響應式原理。
Proxy/Reflect是在ES2015規範中加入的,Proxy能夠更好的攔截對象行爲,Reflect能夠更優雅的操縱對象。 優點在於
說了這麼多咱們先來一個小例子
var obj = new Proxy({}, {
get: function (target, key, receiver) { console.log(`getting ${key}!`); return Reflect.get(target, key, receiver); }, set: function (target, key, value, receiver) { console.log(`setting ${key}!`); return Reflect.set(target, key, value, receiver); } }) obj.abc = 132 複製代碼
這樣寫若是你修改obj中的值,就會打印出來。
也就是說若是對象被修改就會得的被響應。
固然咱們須要的響應就是從新更新視圖也就是從新運行render方法。
首先製造一個抽象的數據響應函數
// 定義響應函數
let effective observed = new Proxy(config.data(), { set(target, key, value, receiver) { const ret = Reflect.set(target, key, value, receiver) // 觸發函數響應 effective() return ret }, }) 複製代碼
在初始化的時候咱們設置響應動做爲渲染視圖
const dom = document.querySelector(container)
// 設置響應動做爲渲染視圖 effective = () => render(observed, dom) render(observed, dom) 複製代碼
瀏覽器視圖的變化,主要體如今對輸入項變化的監聽上,因此只須要經過綁定監聽事件就能夠了。
document.querySelector('input').addEventListener('keyup', function () {
data.message = this.value }) 複製代碼
<html lang="en">
<body> <div id='app'></div> <script> const App = { // 視圖 template: ` <input v-model="message"/> <button @click='click'>{{message}}</button> `, data() { return { message: 'Hello Vue 3!!' } }, methods: { click() { this.message = this.message.split('').reverse().join('') } } } const Vue = { createApp(config) { // 編譯過程 const compile = (template) => (observed, dom) => { // 從新渲染 let input = dom.querySelector('input') if (!input) { input = document.createElement('input') input.setAttribute('value', observed.message) input.addEventListener('keyup', function () { observed.message = this.value }) dom.appendChild(input) } let button = dom.querySelector('button') if (!button) { console.log('create button') button = document.createElement('button') button.addEventListener('click', () => { return config.methods.click.apply(observed) }) dom.appendChild(button) } button.innerText = observed.message } // 生成渲染函數 const render = compile() // 定義響應函數 let effective // 數據劫持 observed = new Proxy(config.data(), { set(target, key, value, receiver) { const ret = Reflect.set(target, key, value, receiver) // 觸發函數響應 effective() return ret }, }) return { mount: function (container) { const dom = document.querySelector(container) effective = () => render(observed, dom) render(observed, dom) } } } } const { createApp } = Vue createApp(App).mount('#app') </script> </body> </html> 複製代碼
OK今天寫到這,終於完成了第一步雖然大部分還都是固定的至少把大致結構搞定了。
這個章節咱們主要看看compile這個功能。
上文已經說過編譯函數的功能
// 編譯函數
// 輸入值爲視圖模板 const compile = (template) => { //渲染函數 return (observed, dom) => { // 渲染過程 } } 複製代碼
簡單的說就是
輸入:視圖模板
輸出:渲染函數
細分起來還能夠分爲三個個小步驟
Parse 模板字符串 -> AST(Abstract Syntax Treee)抽象語法樹
Transform 轉換標記 譬如 v-bind v-if v-for的轉換
Generate AST -> 渲染函數
// 模板字符串 -> AST(Abstract Syntax Treee)抽象語法樹
let ast = parse(template) // 轉換處理 譬如 v-bind v-if v-for的轉換 ast = transfer(ast) // AST -> 渲染函數 return generator(ast) 複製代碼
咱們能夠經過在線版的VueTemplateExplorer感覺一下
https://vue-next-template-explorer.netlify.com/
「
解析器的工做原理其實就是一連串的正則匹配。
好比:
標籤屬性的匹配
class="title"
class='title'
class=title
const attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]*)=("([^"]*)"|'([^']*)'|([^\s"'=<>`]+)/
"class=abc".match(attr); // output (6) ["class=abc", "class", "abc", undefined, undefined, "abc", index: 0, input: "class=abc", groups: undefined] "class='abc'".match(attr); // output (6) ["class='abc'", "class", "'abc'", undefined, "abc", undefined, index: 0, input: "class='abc'", groups: undefined] 複製代碼
這個等實現的時候再仔細講。能夠參考一下文章。
那對於咱們的項目來說就能夠寫成這個樣子
// <input v-model="message"/>
// <button @click='click'>{{message}}</button> // 轉換後的AST語法樹 const parse = template => ({ children: [{ tag: 'input', props: { name: 'v-model', exp: { content: 'message' }, }, }, { tag: 'button', props: { name: '@click', exp: { content: 'message' }, }, content:'{{message}}' } ], }) 複製代碼
前一段知識作的是抽象語法樹,對於Vue3模板的特別轉換就是在這裏進行。
好比:vFor、vOn
在Vue三種也會細緻的分爲兩個層級進行處理
compile-core 核心編譯邏輯
AST-Parser
基礎類型解析 v-for 、v-on
compile-dom 針對瀏覽器的編譯邏輯
v-html
v-model
v-clock
const transfer = ast => ({
children: [{ tag: 'input', props: { name: 'model', exp: { content: 'message' }, }, }, { tag: 'button', props: { name: 'click', exp: { content: 'message' }, }, children: [{ content: { content: 'message' }, }] } ], }) 複製代碼
生成器其實就是根據轉換後的AST語法樹生成渲染函數。固然針對相同的語法樹你能夠渲染成不一樣結果。好比button你但願渲染成 button仍是一個svg的方塊就看你的喜歡了。這個就叫作自定義渲染器。這裏咱們先簡單寫一個固定的Dom的渲染器佔位。到後面實現的時候我在展開處理。
const generator = ast => (observed, dom) => {
// 從新渲染 let input = dom.querySelector('input') if (!input) { input = document.createElement('input') input.setAttribute('value', observed.message) input.addEventListener('keyup', function () { observed.message = this.value }) dom.appendChild(input) } let button = dom.querySelector('button') if (!button) { console.log('create button') button = document.createElement('button') button.addEventListener('click', () => { return config.methods.click.apply(observed) }) dom.appendChild(button) } button.innerText = observed.message } 複製代碼
喜歡的點贊👍👍👍👍👍 保持關注
我會持續更新的
本文使用 mdnice 排版
視頻講解 b站視頻 www.bilibili.com/video/BV1fa…
具體時間能夠看你們能夠看看官方時間表。 官方時間表
目前在Vue3處於Beta版本,後面主要是處理穩定性問題。也就是說主要Api不會有不少改進。尤大神從直播中說雖然不少想法,可是大的變化最快也會出如今3.1上面了。因此目前的版本應該應該和正式版差別很小了。 看來Q2能發佈的可能性極大。