Vue渲染器的簡(jiǎn)單實(shí)現(xiàn)過(guò)程需要做到的幾點(diǎn)是:
1.生成vnode慧邮;
2.把vnode掛載到真實(shí)的dom上面;
3.vnode之間進(jìn)行patch舟陆,并掛載到真實(shí)dom误澳;
1.生成vnode
vnode本質(zhì)是一個(gè)js對(duì)象,對(duì)象中有非常多的屬性描述節(jié)點(diǎn)秦躯,vue源碼中創(chuàng)建的vnode屬性非常多忆谓,但是在簡(jiǎn)單模擬中,我只選取其中最為重要的三個(gè)屬性tag(節(jié)點(diǎn)類型),props(包裹了事件監(jiān)聽函數(shù)踱承、樣式相關(guān)的class倡缠、style等屬性),children(子節(jié)點(diǎn))進(jìn)行模擬哨免。
要生成vnode,需要定義h函數(shù)昙沦,該函數(shù)接收上述屬性作為參數(shù)琢唾,再包裹為對(duì)象返回回來(lái)從而得到vnode。
2.掛載
定義一個(gè)mount函數(shù)桅滋,將生成的vnode作為參數(shù)傳進(jìn)去的同時(shí)慧耍,vnode掛載到哪里去呢?需要指定對(duì)應(yīng)的容器一起傳入丐谋。
創(chuàng)建對(duì)應(yīng)tag的真實(shí)的element芍碧,處理虛擬節(jié)點(diǎn)的props(暫時(shí)只考慮界節(jié)點(diǎn)的事件監(jiān)聽和其他不需要做額外處理的屬性),處理節(jié)點(diǎn)為文本類型(children為string)的情況号俐,并掛載到傳入的容器
在頁(yè)面掛載一個(gè)試一試
文本類型順利掛載之后泌豆,假如節(jié)點(diǎn)有他的子節(jié)點(diǎn)呢,那么遍歷子節(jié)點(diǎn)吏饿,并調(diào)用mount函數(shù)掛載到該節(jié)點(diǎn)上(相當(dāng)于一個(gè)遞歸操作)
傳入子節(jié)點(diǎn)試試:
基本上踪危,掛載功能實(shí)現(xiàn)了,下一步是視圖發(fā)生變化猪落,vnode之間patch的實(shí)現(xiàn)
3.vnode之間的patch
首先贞远,判斷兩個(gè)vnode之間是不是相同的類型,如果不是相同的類型笨忌,簡(jiǎn)單粗暴的將之前的el卸載掉蓝仲,掛載新的。
如果vnode是相同類型:
取出el對(duì)象官疲,并處理所有props(新的props就加上负敏,舊的就刪掉)
處理children:假如新children是string袜茧,給到el(也可以說(shuō)是替換)桐愉;如果新children是數(shù)組而克,再去判斷舊children是string還是數(shù)組,
舊children是string:將文本清空维费,將新children遍歷掛載到el果元;舊children是數(shù)組,進(jìn)行patch犀盟。
children之間的pacth操作就是源碼中沒有key的patch噪漾,感興趣可以查看我的patch過(guò)程中有無(wú)key屬性的相關(guān)源碼梳理
模擬一下:
效果:
patch函數(shù)代碼如下:
const patch = (n1, n2) => {
? // 先判斷類型tag是否一樣
? if (n1.tag !== n2.tag) {
? ? const n1ElParent = n1.el.parentElement
? ? n1ElParent.removeChild(n1.el)
? ? mount(n2, n1ElParent)
? } else {
? ? // 1.取出element對(duì)象,并且在n2中進(jìn)行 保存
? ? const el = n2.el = n1.el
? ? // 2.處理props
? ? const oldProps = n1.props || {}
? ? const newProps = n2.props || {}
? ? // 2.1獲取所有newprops 添加到el
? ? for (const key in newProps) {
? ? ? const oldValue = oldProps[key]
? ? ? const newValue = newProps[key]
? ? ? if (newValue !== oldValue) {
? ? ? ? if (key.startsWith('on')) { // 對(duì)事件監(jiān)聽的判斷
? ? ? ? ? el.addEventListener(key.slice(2).toLowerCase(), newValue)
? ? ? ? } else {
? ? ? ? ? el.setAttribute(key, newValue)
? ? ? ? }
? ? ? }
? ? }
? ? // 2.2刪除舊的props
? ? for (const key in oldProps) {
? ? ? if (!(key in newProps)) {
? ? ? ? const value = oldProps[key]
? ? ? ? if (key.startsWith('on')) { // 對(duì)事件監(jiān)聽的判斷
? ? ? ? ? el.removeEventListener(key.slice(2).toLowerCase(), value)
? ? ? ? } else {
? ? ? ? ? el.removeAttribute(key)
? ? ? ? }
? ? ? }
? ? }
? ? // 3.處理children
? ? const oldChildren = n1.children || []
? ? const newChildren = n2.children || []
? ? if (typeof newChildren === 'string') { // 情況1: newChildren本身是string
? ? ? if (typeof oldChildren === 'string') {
? ? ? ? if (oldChildren !== newChildren) {
? ? ? ? ? el.textContent = newChildren
? ? ? ? }
? ? ? } else {
? ? ? ? el.innerHTML = newChildren
? ? ? }
? ? } else { // 情況2:newChildren本身是數(shù)組
? ? ? if (typeof oldChildren === 'string') {
? ? ? ? el.innerHTML = ''
? ? ? ? newChildren.forEach(item => {
? ? ? ? ? mount(item, el)
? ? ? ? })
? ? ? } else {
? ? ? ? // oldChildren: [v1, v2, v3, v6]
? ? ? ? // newChildren: [v1, v5, v6, v8, v9]
? ? ? ? // 1.前面又相同節(jié)點(diǎn)的元素進(jìn)行patch操作
? ? ? ? const commonLength = Math.min(oldChildren.length, newChildren.length)
? ? ? ? for (let i = 0; i<commonLength; i++) {
? ? ? ? ? patch(oldChildren[i], newChildren[i])
? ? ? ? }
? ? ? ? // 2.newChildren.length > oldChildren.length
? ? ? ? if(newChildren.length > oldChildren.length) {
? ? ? ? ? newChildren.slice(oldChildren.length).forEach(item=>{
? ? ? ? ? ? mount(item,el)
? ? ? ? ? })
? ? ? ? }
? ? ? ? // 3.newChildren.length < oldChildren.length
? ? ? ? if(newChildren.length < oldChildren.length) {
? ? ? ? ? oldChildren.slice(newChildren.length).forEach(item=>{
? ? ? ? ? ? console.log(item.el)
? ? ? ? ? ? el.removeChild(item.el)
? ? ? ? ? })
? ? ? ? }
? ? ? }
? ? }
? }
}