建立一個簡單的迷你Vue3-2
阿新 • • 發佈:2020-07-19
經過前面兩章,我們已經建立了模板渲染系統和響應式資料系統。
那麼接下來我們就要將他們組合在一起,以及加上一些生命週期的處理等等。來合成一個真正的mini-vue3.
首先是要將之前兩塊內容整合到一起:
<html> <head> <title>Mini-Vue3</title> </head> <body> <div id="app"></div> <script> // compile & render system// 渲染函式,將函式描述的模版轉換為物件表述的json物件 function h(tag, props, children) { return { tag, props, children, }; } // 將指定的vnode裝在到container上 function mount(vnode, container) { const el= document.createElement(vnode.tag); // 儲存vnode的dom引用 vnode.el = el; // props if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key]; el.setAttribute(key, value); } }// children if (vnode.children) { // 區分字串還是array,簡單處理 if (typeof vnode.children === 'string') { el.textContent = vnode.children; } else { vnode.children.forEach(child => { mount(child, el); }); } } container.appendChild(el); } // 比對已有vnode和新node,進行更新 function patch(oldNode, newNode) { if (oldNode.tag === newNode.tag) { const el = oldNode.el;
newNode.el=oldNode.el;
// 如果tag相同,則需要進行下一步的各種判斷,props的判斷,更新,children的判斷,更新 // 首先處理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判斷新props,如果舊的有,則更新,無,則新增 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的與舊的不同,則更新 el.setAttribute(key, newValue); } } // 然後判斷舊props裡面,如果新的沒有,則刪除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然後將props設定為新 oldNode.props = newProps; // 接著處理children, 這裡我們簡單處理,認為children要麼是string,要麼是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 對於舊子節點為string的情況 if (typeof newChildren === 'string') { // 如果新子節點也是string,則直接替換即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新節點是array // 清空原有子節點內容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 舊節點是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 兩個都是array,就簡單處理,只是比對同順序 // 先取兩個陣列同樣長度對比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替換新元素 patch(oldChildren[i], newChildren[i]); } // 然後如果舊陣列還有,則移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新陣列還有,則新增多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,則直接替換即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 新增依賴 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依賴更新,通知回撥 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 註冊事件,當依賴的物件發生改變時,觸發effect方法執行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 新增整個物件的依賴 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 獲取具體key的依賴 let dep = currMap.get(key); // 如果沒有新增過則新增依賴 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 為什麼每次讀取都要新增依賴,因為有可能會增加新的依賴 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 類似於之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知執行依賴 dep.notify(); // 因為proxy的set必須要返回一個boolean的值告訴設定成功與否,因此這裡返回系統api的結果即可 return ret; } }; /** * @description 給物件封裝並返回響應式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } </script> </body> </html>
接下來則是要像真正在Vue裡面寫元件那樣來實現這個mini-vue3:
因為現在我們只是實現了基礎的API,但是我們實際使用Vue都是通過元件的形式來組織程式碼的,並且通過Vue的引數來將元件傳入並掛<html>
<head> <title>Mini-Vue3</title> </head> <body> <div id="app"></div> <script> // compile & render system // 渲染函式,將函式描述的模版轉換為物件表述的json物件 function h(tag, props, children) { return { tag, props, children, }; } // 將指定的vnode裝在到container上 function mount(vnode, container) { const el = document.createElement(vnode.tag); // 儲存vnode的dom引用 vnode.el = el; // props if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key]; el.setAttribute(key, value); } } // children if (vnode.children) { // 區分字串還是array,簡單處理 if (typeof vnode.children === 'string') { el.textContent = vnode.children; } else { vnode.children.forEach(child => { mount(child, el); }); } } container.appendChild(el); } // 比對已有vnode和新node,進行更新 function patch(oldNode, newNode) { if (oldNode.tag === newNode.tag) { const el = oldNode.el;
newNode.el = oldNode.el;
// 如果tag相同,則需要進行下一步的各種判斷,props的判斷,更新,children的判斷,更新 // 首先處理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判斷新props,如果舊的有,則更新,無,則新增 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的與舊的不同,則更新 el.setAttribute(key, newValue); } } // 然後判斷舊props裡面,如果新的沒有,則刪除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然後將props設定為新 oldNode.props = newProps; // 接著處理children, 這裡我們簡單處理,認為children要麼是string,要麼是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 對於舊子節點為string的情況 if (typeof newChildren === 'string') { // 如果新子節點也是string,則直接替換即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新節點是array // 清空原有子節點內容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 舊節點是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 兩個都是array,就簡單處理,只是比對同順序 // 先取兩個陣列同樣長度對比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替換新元素 patch(oldChildren[i], newChildren[i]); } // 然後如果舊陣列還有,則移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新陣列還有,則新增多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,則直接替換即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 新增依賴 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依賴更新,通知回撥 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 註冊事件,當依賴的物件發生改變時,觸發effect方法執行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 新增整個物件的依賴 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 獲取具體key的依賴 let dep = currMap.get(key); // 如果沒有新增過則新增依賴 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 為什麼每次讀取都要新增依賴,因為有可能會增加新的依賴 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 類似於之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知執行依賴 dep.notify(); // 因為proxy的set必須要返回一個boolean的值告訴設定成功與否,因此這裡返回系統api的結果即可 return ret; } }; /** * @description 給物件封裝並返回響應式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } // 我們現在來按照Vue元件的格式定義一個簡單的自定義元件 const App = { data: reactive({ count: 0, }), render() { return h('div', null, App.data.count); }, }; // 接著我們需要通過一個安裝的方法來將這個元件掛載並且需要 // 在依賴的資料發生變化後重新渲染元件 </script> </body> </html>
因此這裡我們需要建立一個安裝方法來掛載這個元件
<html> <head> <title>Mini-Vue3</title> </head> <body> <div id="app"></div> <script> // compile & render system // 渲染函式,將函式描述的模版轉換為物件表述的json物件 function h(tag, props, children) { return { tag, props, children, }; } // 將指定的vnode裝在到container上 function mount(vnode, container) { const el = document.createElement(vnode.tag); // 儲存vnode的dom引用 vnode.el = el; // props if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key]; el.setAttribute(key, value); } } // children if (vnode.children) { // 區分字串還是array,簡單處理 if (typeof vnode.children === 'string') { el.textContent = vnode.children; } else { vnode.children.forEach(child => { mount(child, el); }); } } container.appendChild(el); } // 比對已有vnode和新node,進行更新 function patch(oldNode, newNode) { if (oldNode.tag === newNode.tag) { const el = oldNode.el;
newNode.el = oldNode.el; // 如果tag相同,則需要進行下一步的各種判斷,props的判斷,更新,children的判斷,更新 // 首先處理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判斷新props,如果舊的有,則更新,無,則新增 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的與舊的不同,則更新 el.setAttribute(key, newValue); } } // 然後判斷舊props裡面,如果新的沒有,則刪除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然後將props設定為新 oldNode.props = newProps; // 接著處理children, 這裡我們簡單處理,認為children要麼是string,要麼是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 對於舊子節點為string的情況 if (typeof newChildren === 'string') { // 如果新子節點也是string,則直接替換即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新節點是array // 清空原有子節點內容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 舊節點是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 兩個都是array,就簡單處理,只是比對同順序 // 先取兩個陣列同樣長度對比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替換新元素 patch(oldChildren[i], newChildren[i]); } // 然後如果舊陣列還有,則移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新陣列還有,則新增多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,則直接替換即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 新增依賴 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依賴更新,通知回撥 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 註冊事件,當依賴的物件發生改變時,觸發effect方法執行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 新增整個物件的依賴 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 獲取具體key的依賴 let dep = currMap.get(key); // 如果沒有新增過則新增依賴 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 為什麼每次讀取都要新增依賴,因為有可能會增加新的依賴 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 類似於之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知執行依賴 dep.notify(); // 因為proxy的set必須要返回一個boolean的值告訴設定成功與否,因此這裡返回系統api的結果即可 return ret; } }; /** * @description 給物件封裝並返回響應式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } /** * @description 將元件掛載到dom上 */ function mountApp(component, container) { // 先拿到vdom const vdom = component.render(); // 掛載 mount(vdom, container); } // 我們現在來按照Vue元件的格式定義一個簡單的自定義元件 const App = { data: reactive({ count: 1, }), render() { return h('div', null, String(App.data.count)); // 因為前面vdom限定了string }, }; // 接著我們需要通過一個安裝的方法來將這個元件掛載並且需要 // 在依賴的資料發生變化後重新渲染元件 mountApp(App, document.getElementById('app')) </script> </body> </html>
此時可以看下目前的效果:
已經渲染成功了,但是我們還少了一些東西,那就是我們應當要觸發我們依賴的物件的資料變更,並且我們的Dom要能重新渲染,因此我們應當在watchEffect方法中去更新我們的dom,
新的程式碼如下:
<html> <head> <title>Mini-Vue3</title> </head> <body> <div id="app"></div> <script> // compile & render system // 渲染函式,將函式描述的模版轉換為物件表述的json物件 function h(tag, props, children) { return { tag, props, children, }; } // 將指定的vnode裝在到container上 function mount(vnode, container) { const el = document.createElement(vnode.tag); // 儲存vnode的dom引用 vnode.el = el; // props if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key]; el.setAttribute(key, value); } } // children if (vnode.children) { // 區分字串還是array,簡單處理 if (typeof vnode.children === 'string') { el.textContent = vnode.children; } else { vnode.children.forEach(child => { mount(child, el); }); } } container.appendChild(el); } // 比對已有vnode和新node,進行更新 function patch(oldNode, newNode) { if (oldNode.tag === newNode.tag) { const el = oldNode.el;
newNode.el = oldNode.el; // 如果tag相同,則需要進行下一步的各種判斷,props的判斷,更新,children的判斷,更新 // 首先處理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判斷新props,如果舊的有,則更新,無,則新增 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的與舊的不同,則更新 el.setAttribute(key, newValue); } } // 然後判斷舊props裡面,如果新的沒有,則刪除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然後將props設定為新 oldNode.props = newProps; // 接著處理children, 這裡我們簡單處理,認為children要麼是string,要麼是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 對於舊子節點為string的情況 if (typeof newChildren === 'string') { // 如果新子節點也是string,則直接替換即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新節點是array // 清空原有子節點內容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 舊節點是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 兩個都是array,就簡單處理,只是比對同順序 // 先取兩個陣列同樣長度對比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替換新元素 patch(oldChildren[i], newChildren[i]); } // 然後如果舊陣列還有,則移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新陣列還有,則新增多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,則直接替換即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 新增依賴 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依賴更新,通知回撥 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 註冊事件,當依賴的物件發生改變時,觸發effect方法執行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 新增整個物件的依賴 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 獲取具體key的依賴 let dep = currMap.get(key); // 如果沒有新增過則新增依賴 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 為什麼每次讀取都要新增依賴,因為有可能會增加新的依賴 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 類似於之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知執行依賴 dep.notify(); // 因為proxy的set必須要返回一個boolean的值告訴設定成功與否,因此這裡返回系統api的結果即可 return ret; } }; /** * @description 給物件封裝並返回響應式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } /** * @description 將元件掛載到dom上 */ function mountApp(component, container) { // 是否已經掛載過 let isMounted = false; let vdom; // 註冊watchEffect的回撥 // 如果沒有掛載過,則掛載,否則patch watchEffect(() => { if (!isMounted) { vdom = component.render(); mount(vdom, container); } else { const vom2 = component.render(); patch(vdom, vdom2); vdom = vdom2; } }); } // 我們現在來按照Vue元件的格式定義一個簡單的自定義元件 const App = { data: reactive({ count: 1, }), render() { return h('div', null, String(App.data.count)); // 因為前面vdom限定了string }, }; // 接著我們需要通過一個安裝的方法來將這個元件掛載並且需要 // 在依賴的資料發生變化後重新渲染元件 mountApp(App, document.getElementById('app')) </script> </body> </html>
但是因為我們還沒有更新資料觸發effect的機制,因此我們修改下mount方法,引入事件繫結:
<html> <head> <title>Mini-Vue3</title> </head> <body> <div id="app"></div> <script> // compile & render system // 渲染函式,將函式描述的模版轉換為物件表述的json物件 function h(tag, props, children) { return { tag, props, children, }; } // 將指定的vnode裝在到container上 function mount(vnode, container) { const el = document.createElement(vnode.tag); // 儲存vnode的dom引用 vnode.el = el; // props if (vnode.props) { for (const key in vnode.props) { const value = vnode.props[key]; if (key.indexOf('on') === 0) { // 如果屬性以on開頭則說明是方法 const funName = key.substring(2).toLowerCase(); el.addEventListener(funName, value); } else { el.setAttribute(key, value); } } } // children if (vnode.children) { // 區分字串還是array,簡單處理 if (typeof vnode.children === 'string') { el.textContent = vnode.children; } else { vnode.children.forEach(child => { mount(child, el); }); } } container.appendChild(el); } // 比對已有vnode和新node,進行更新 function patch(oldNode, newNode) { if (oldNode.tag === newNode.tag) { const el = oldNode.el; newNode.el = oldNode.el; // 如果tag相同,則需要進行下一步的各種判斷,props的判斷,更新,children的判斷,更新 // 首先處理props const oldProps = oldNode.props || {}; const newProps = newNode.props || {}; // 首先判斷新props,如果舊的有,則更新,無,則新增 for (const key in newProps) { const oldValue = oldProps[key]; const newValue = newProps[key]; if (newValue !== oldValue) { // 如果新的與舊的不同,則更新 el.setAttribute(key, newValue); } } // 然後判斷舊props裡面,如果新的沒有,則刪除 for (const key in oldProps) { if (!(key in newProps)) { el.removeAttribute(key); } } // 然後將props設定為新 oldNode.props = newProps; // 接著處理children, 這裡我們簡單處理,認為children要麼是string,要麼是array const oldChildren = oldNode.children; const newChildren = newNode.children; if (typeof oldChildren === 'string') { // 對於舊子節點為string的情況 if (typeof newChildren === 'string') { // 如果新子節點也是string,則直接替換即可 el.textContent = newChildren; oldNode.children = newChildren; } else { // 新節點是array // 清空原有子節點內容 el.innerHTML = ''; newChildren.forEach(child => { mount(child, el); }); oldNode.children = newChildren; } } else { // 舊節點是array if (typeof newChildren === 'string') { el.innerHTML = newChildren; oldNode.children = newChildren; } else { // 兩個都是array,就簡單處理,只是比對同順序 // 先取兩個陣列同樣長度對比,直接patch const sameLength = Math.min(oldChildren.length, newChildren.length); for (let i = 0; i < sameLength; i++) { // 替換新元素 patch(oldChildren[i], newChildren[i]); } // 然後如果舊陣列還有,則移除多出部分 if (oldChildren.length > sameLength) { oldChildren.slice(sameLength).forEach(child => { el.removeChild(child.el); }); } // 如果新陣列還有,則新增多出部分 if (newChildren.length > sameLength) { newChildren.slice(sameLength).forEach(child => { mount(child, el); }); } } } } else { // 如果tag都不同,則直接替換即可 const parent = oldNode.el.parentNode; mount(newNode, parent); parent.removeChild(oldNode.el); oldNode.el = newNode.el; } } // reactive system let activeEffect = null; class Dep { subscribers = new Set(); // 新增依賴 depend() { if (activeEffect) { this.subscribers.add(activeEffect); } } // 依賴更新,通知回撥 notify() { this.subscribers.forEach(effect => { effect(); }); } } /** * @decription 註冊事件,當依賴的物件發生改變時,觸發effect方法執行 */ function watchEffect(effect) { activeEffect = effect; effect(); activeEffect = null; } const targetMap = new WeakMap(); function getDep(target, key) { // 新增整個物件的依賴 let currMap = targetMap.get(target); if (!currMap) { currMap = new Map(); targetMap.set(target, currMap); } // 獲取具體key的依賴 let dep = currMap.get(key); // 如果沒有新增過則新增依賴 if (!dep) { dep = new Dep(); currMap.set(key, dep); } return dep; } const reactiveHandlers = { get(target, key, receiver) { const dep = getDep(target, key); // 為什麼每次讀取都要新增依賴,因為有可能會增加新的依賴 dep.depend(); return Reflect.get(target, key, receiver); }, set(target, key, value, receiver) { const dep = getDep(target, key); // 類似於之前的Object.set const ret = Reflect.set(target, key, value, receiver); // 通知執行依賴 dep.notify(); // 因為proxy的set必須要返回一個boolean的值告訴設定成功與否,因此這裡返回系統api的結果即可 return ret; } }; /** * @description 給物件封裝並返回響應式代理 */ function reactive(oldObj) { return new Proxy(oldObj, reactiveHandlers); } /** * @description 將元件掛載到dom上 */ function mountApp(component, container) { // 是否已經掛載過 let isMounted = false; let vdom; // 註冊watchEffect的回撥 // 如果沒有掛載過,則掛載,否則patch watchEffect(() => { if (!isMounted) { vdom = component.render(); mount(vdom, container); isMounted = true; } else { const vdom2 = component.render(); patch(vdom, vdom2); vdom = vdom2; } }); } // 我們現在來按照Vue元件的格式定義一個簡單的自定義元件 const App = { data: reactive({ count: 1, }), render() { return h('div', { onClick: () => { // 新增點選事件方法 this.data.count += 1; } }, String(this.data.count)); // 因為前面vdom限定了string }, }; // 接著我們需要通過一個安裝的方法來將這個元件掛載並且需要 // 在依賴的資料發生變化後重新渲染元件 mountApp(App, document.getElementById('app')) </script> </body> </html>
然後效果ok,沒有問題:
至此我們的mini-vue3已經跑起來了,只不過我們模板是需要我們自己去寫render function,如果把編譯系統也加入,使得我們直接通過模板寫vue元件,那就幾乎是一個完整功能的vue了。
後續或許要來實現他,我們的這一個小系列已經完成了,接下來會基於這個mini-vue3來寫Vue3中的一些新特性的實現。