您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“mini-vue渲染的實現(xiàn)方法”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠?qū)W有所成!
前言
目標
第一步:
第二步:
第三步:
第四步:
總結
目前的主流框架Vue、React 都是通過 Virtual Dom(虛擬Dom)來實現(xiàn)的,通過Virtual Dom技術提高頁面的渲染效率。Vue中我們通過在 template 模板中編寫html代碼,React中我們通過在內(nèi)部的一個 render 函數(shù)里編寫html代碼,這個函數(shù)通過 jsx 編譯后,實際會輸出一個h函數(shù),也就是我們的 Virtual Dom(虛擬Dom),下面簡單來實現(xiàn)一個虛擬dom渲染真實dom,以及更新的方法。
主要實現(xiàn)以下三個功能:
通過h函數(shù)返回Vnodes;
通過 mount 函數(shù)將 虛擬dom 掛載到真實節(jié)點上;
通過 patch 函數(shù)通過 newVnodes 與 oldVnodes比較來實現(xiàn)dom的更新;
在body標簽內(nèi)創(chuàng)建一個id為app的節(jié)點,后面會將虛擬節(jié)點掛載到這個節(jié)點上,而renderer.js用來實現(xiàn)上面三個功能。
<body> <div id="app"></div> <script src="./renderer.js"></script> </body>
編寫h函數(shù),用來返回tag(標簽元素)、props(屬性對象)、children(子節(jié)點),簡單來說虛擬Dom就是一個普通javaScript對象。
// renderer.js const h = (tag, props, children) => { return { tag, props, children } }
那么通過這個h函數(shù),我們簡單來看看下面一段代碼會輸出什么?
const vdom = h("div", {class: "header"}, [ h("h3", null, "Hello World"), h("h3", {id: 0}, h("span", null, "啦啦啦啦")) // 當props沒有值,必須傳一個null,不能不傳 ]); console.log(vdom);
由下圖可以看出,通過h函數(shù),給我們返回了一個javaScript對象,這個便是 Virtual Dom(虛擬Dom),形成樹狀節(jié)點。
那么我們拿到這個vnodes,如何掛載到真實節(jié)點上呢?下面我們來看看第三步
我們先創(chuàng)建一個 mount 函數(shù),需要傳入兩個參數(shù),第一個是我們剛剛通過h函數(shù)返回的vnodes,第二個參數(shù)是我們需要將這些vnode掛載到哪個節(jié)點上,接下來看代碼:
const mount = (vnodes, app) => { // 通過vnodes里面的tag值,比如("div", "h3"),創(chuàng)建一個節(jié)點 // 同樣在vnodes對象里保存一份真實dom,方便以后進行更新,新增等操作 const el = vnodes.el = document.createElement(vnodes.tag); // 拿到這個節(jié)點后,我們通過判斷props值,進行添加屬性 if (vnodes.props) { for (let key in vnodes.props) { // 這兒通過拿到props中key值后,在做判斷 let value = vnodes.props[key]; if (key.startsWith('on')) { // 比如用戶寫了一個onClick="changeData",處理為監(jiān)聽函數(shù)的事件 el.addEventListener(key.slice(2).toLowerCase(), value) } else { el.setAttribute(key, value) } // 這下面還有些判斷,比如指令啊v-if等等,進行邊界化處理 } } // 處理完props后,最后是Children節(jié)點 if (vnodes.children) { if (typeof vnodes.children === 'string') { // 如果這兒是個字符串類型,那么就可以直接添加到節(jié)點中去 el.textContent = vnodes.children } else { // 這種情況為數(shù)組類型,含有子節(jié)點,通過遍歷,再生成子節(jié)點 vnodes.children.forEach(vnode => { // 通過遞歸,再次將子節(jié)點掛載到當前節(jié)點上去 mount(vnode, el) }) } } // 最終將這個真實節(jié)點掛載到我們傳入的app節(jié)點中去 app.appendChild(el); } const app = document.querySelector("#app") mount(vdom, app)
我們來看看通過mount函數(shù)掛載的實際效果:
那么到這里,我們就已經(jīng)實現(xiàn)了通過虛擬dom來創(chuàng)建真實dom了,那在vue當中是如何對這些dom進行更新操作呢,接下來我們再創(chuàng)建一個 patch函數(shù)(更新):
通過patch函數(shù),我們需要傳入兩個參數(shù)(vnodes1、vnodes2),分別是新的虛擬dom和舊的虛擬dom,通過比較新舊虛擬dom,來指定更新哪些節(jié)點。(這兒不考慮key值,需要參考key值可以查看鏈接:http://kemok4.com/article/219078.htm)
// patch函數(shù) n1: 舊節(jié)點、 n2:新節(jié)點 // 在vue源碼中,舊vnode,新vnode分別用n1, n2表示 const patch = (n1, n2) => { // 在上面我們通過mount函數(shù)給n2添加了節(jié)點屬性el,綁定到n2上 const el = n2.el = n1.el // 首先,還是從兩個中的tag入手 if (n1.tag == n2.tag) { // n1、n2的tag相同,再對比props const n1Props = n1.props || {}; const n2Props = n2.props || {}; // 分別取到n1,n2中的props,進行比較 for (let key in n2Props) { // 取出n2中所有key,判斷n2的key值和n1key值是否相同 const n1Value = n1Props[key] || ''; const n2Value = n2Props[key] || ''; if (n1Value !== n2Value) { if (key.startsWith('on')) { // 比如用戶寫了一個onClick="changeData",處理為監(jiān)聽函數(shù)的事件 el.addEventListener(key.slice(2).toLowerCase(), n2Value) } else { el.setAttribute(key, n2Value) } } // 相同則不作處理 } for (let key in n1Props) { const oldValue = n1Props[key]; if (!(key in n2Props)) { if (key.startsWith('on')) { el.removeEventListener(key.slice(2).toLowerCase(), oldValue) } else { el.removeAttribute(key) } } } } else { // tag不同,拿到n1的父節(jié)點 const n1Parent = n1.el.parentElement; // 通過removeChild將舊節(jié)點從父節(jié)點中移除,然后將n2掛載到父節(jié)點中 n1Parent.removeChild(n1.el); //n1.el是通過mount函數(shù)往對象里添加的真實dom節(jié)點 mount(n2, n1Parent) } // 最后處理children,相對于來說復雜些 // children可以為字符串也可以為數(shù)組,那么先看字符串時怎么處理 const n1Children = n1.children || []; const n2Children = n2.children || []; if (typeof n2Children === "string") { // 如果新節(jié)點內(nèi)容為字符串,直接使用innerhtml進行替換 el.innerHtml = n2Children; } else { // 下面情況是n2.children為數(shù)組情況時 if (typeof n1.children === "string") { // n1.children為字符串,n2.children為數(shù)組 el.innerHtml = ''; // 先將節(jié)點內(nèi)容情況,再講新的內(nèi)容添加進去 mount(n2.children, el) } else { // 兩種都為數(shù)組類型時,這兒不考慮key值 const minLength = Math.min(n1Children.length, n2Children.length); for (let i = 0 ; i < minLength ; i++) { patch(n1Children[i], n2Children[i]); } if(n2Children.length > n1Children.length) { n2Children.slice(minLength).forEach(item => { mount(item, el) }) } if(n2Children.length < n1Children.length) { n1Children.slice(minLength).forEach(item => { el.removeChild(item.el) }) } } } }
上面簡單的實現(xiàn)了patch的作用,其實就是我們說的diff算法(當然這兒沒有考慮key值的情況,只能兩個依次比較),同一層級進行比較。現(xiàn)在模擬演示一下,看看是否能更新成功:
const vdom = h("div", {class: "header"}, [ h("h3", null, "Hello World"), h("h3", {id: 0}, [h("span", null, "啦啦啦啦")]) // 當props沒有值,必須傳一個null,不能不傳 ]); const app = document.querySelector("#app") mount(vdom, app) setTimeout(()=> { // 3秒后向patch傳入新舊Vnodes const vdom1 = h("div", {class: "header"}, [ h("h4", null, "Hello World"), h("span", null, "哈哈哈") ]) patch(vdom, vdom1) },3000)
通過下圖,我們可以看到已經(jīng)簡單的實現(xiàn)了虛擬dom更新節(jié)點。
簡單的實現(xiàn)了下虛擬Dom生成真實節(jié)點,然后通過patch進行更新。再去看看源碼,就能更好的理解vue的渲染器是如何實現(xiàn)的了。
“mini-vue渲染的實現(xiàn)方法”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關的知識可以關注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權請聯(lián)系站長郵箱:is@yisu.com進行舉報,并提供相關證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權內(nèi)容。