無法處理的 Props 的補丁
在本章中,讓我們為目前無法處理的 Props 實作補丁。 以下是一些需要處理的 Props 示例,但請嘗試透過參考原始實作來實作它們,同時自己填補缺失的部分! 透過這樣做,它應該變得更加實用!
沒有什麼特別新的東西。基於我們到目前為止所做的,應該能夠充分實作它。
我想關注的是 runtime-dom/modules 的實作。
新舊比較
目前,更新只能基於 n2 的 props 進行。 讓我們基於 n1 和 n2 進行更新。
const oldProps = n1.props || {}
const newProps = n2.props || {}存在於 n1 但不存在於 n2 中的 Props 應該被刪除。 另外,如果即使兩者都存在但值相同,也不需要補丁,所以跳過它。
class / style(注意)
有多種綁定 class 和 style 的方法。
<p class="static property">hello</p>
<p :class="'dynamic property'">hello</p>
<p :class="['dynamic', 'property', 'array']">hello</p>
<p :class="{ dynamic: true, property: true, array: true}">hello</p>
<p class="static property" :class="'mixed dynamic property'">hello</p>
<p style="static: true;" :style="{ mixed-dynamic: 'true' }">hello</p>要實作這些,需要在基礎模板編譯器部分解釋的 transform 概念。 只要不偏離原始 Vue 的設計,它可以在任何地方實作,但我們在這裡跳過它,因為我們想在本書中遵循原始 Vue 的設計。
innerHTML / textContent
innerHTML 和 textContent 與其他 Props 相比有點特殊。
這是因為如果具有此 Prop 的元素有子元素,它們需要被卸載。
例如,考慮以下情況:
h('div', { innerHTML: '<p>hello</p>' }, [
h(SomeComponent, {}, [])
])在這種情況下,div 元素的內容將被 innerHTML 覆蓋為 <p>hello</p>。
然而,作為 children 傳遞的 SomeComponent 已經存在於虛擬 DOM 中,如果不正確卸載它,將會發生以下問題:
- 事件監聽器不會被移除
- 元件生命週期鉤子(如 onUnmounted)不會被呼叫
- 可能導致記憶體洩漏
因此,在設定 innerHTML 或 textContent 時,需要卸載現有的子元素。
實作
首先,擴充 patchProp 的型別定義以接受 prevChildren 和 unmountChildren。
~/packages/runtime-core/renderer.ts
export interface RendererOptions<HostNode = RendererNode, HostElement = RendererElement> {
patchProp(
el: HostElement,
key: string,
prevValue: any,
nextValue: any,
prevChildren?: VNode<HostNode>[], // 加入
unmountChildren?: (children: VNode<HostNode>[]) => void, // 加入
): void;
// ...
}接下來,在 patchDOMProp 函式中實作 innerHTML/textContent 的處理。
~/packages/runtime-dom/modules/props.ts
export function patchDOMProp(
el: any,
key: string,
value: any,
prevChildren: any,
unmountChildren: any,
) {
if (key === 'innerHTML' || key === 'textContent') {
// 如果存在子元素則卸載
if (prevChildren) {
unmountChildren(prevChildren)
}
el[key] = value == null ? '' : value
return
}
// ... (其他 props 的處理)
}然後,在從 patchProp 呼叫 patchDOMProp 時傳遞 prevChildren 和 unmountChildren。
~/packages/runtime-dom/patchProp.ts
export const patchProp: DOMRendererOptions['patchProp'] = (
el,
key,
prevValue,
nextValue,
prevChildren,
unmountChildren,
) => {
if (key === 'style') {
patchStyle(el, prevValue, nextValue)
} else if (isOn(key)) {
patchEvent(el, key, nextValue)
} else if (shouldSetAsProp(el, key)) {
patchDOMProp(el, key, nextValue, prevChildren, unmountChildren) // 傳遞 prevChildren, unmountChildren
} else {
patchAttr(el, key, nextValue)
}
}最後,在 renderer.ts 中呼叫 hostPatchProp 時傳遞適當的參數。
~/packages/runtime-core/renderer.ts 的 mountElement 和 patchElement
const mountElement = (vnode: VNode, container: RendererElement, anchor: RendererElement | null) => {
let el: RendererElement
const { type, props } = vnode
el = vnode.el = hostCreateElement(type as string)
mountChildren(vnode.children as VNode[], el, anchor)
if (props) {
for (const key in props) {
hostPatchProp(
el,
key,
null,
props[key],
vnode.children as VNode[], // 加入
unmountChildren, // 加入
)
}
}
hostInsert(el, container)
}現在,當使用 innerHTML 或 textContent 時,現有的子元素將被正確卸載。
到此為止的原始碼: chibivue (GitHub)
