前面兩個章,我們知道到了.vue文件最終會通過vue-loader進行拆解成下面格式:
import { render } from "./App.vue?vue&type=template&id=7ba5bd90&scoped=true"
import script from "./App.vue?vue&type=script&lang=js"
export * from "./App.vue?vue&type=script&lang=js"
import "./App.vue?vue&type=style&index=0&id=7ba5bd90&scoped=true&lang=css"
script.render = render
script.__scopeId = "data-v-7ba5bd90"
/* hot reload */
...
script.__file = "src/App.vue"
export default script
PS:在下文將會使用
組件
特指上面形式的(即.vue編譯出來的)結構
當我們在代碼中寫:
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
好了開始我們今天的表演
1. createApp及mount
export function createAppAPI<HostElement>(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction<HostElement> {
return function createApp(rootComponent, rootProps = null) {
const context = createAppContext()
const installedPlugins = new Set()
let isMounted = false
const app: App = {
//這里將rootComponent保存
_component: rootComponent as Component,
_props: rootProps,
_container: null,
_context: context,
//congfig,use,mixin,component,directive 方法省略
...
mount(rootContainer: HostElement, isHydrate?: boolean): any {
if (!isMounted) {
// 傳入rootComponent創(chuàng)建VNode
const vnode = createVNode(rootComponent as Component, rootProps)
vnode.appContext = context
//渲染
if (isHydrate && hydrate) {
hydrate(vnode as VNode<Node, Element>, rootContainer as any)
} else {
render(vnode, rootContainer)
}
isMounted = true
app._container = rootContainer
return vnode.component!.proxy
}
},
// unmount,provide 省略
...
}
return app
}
}
通過上面我們看到:
- 將在
createApp
中傳入root組件
及props
- 在
mount
中如掛載并渲染
2.1 通過root組件
創(chuàng)建VNode
2.2 通過在生成createApp方法時
傳入的render
進行渲染
createVNode做了什么
function _createVNode(
// 注意這里type 即可以通過傳入type也可以對ClassComponent處理
type: VNodeTypes | ClassComponent,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null
): VNode {
// 處理props
if (props) {
// 如果props是響應式數據
if (isReactive(props) || SetupProxySymbol in props) {
//clone為普通形式數據
props = extend({}, props)
}
let { class: klass, style } = props
if (klass && !isString(klass)) {
props.class = normalizeClass(klass)
}
if (isObject(style)) {
// 如果props是響應式數據纬朝,clone為普通形式數據
if (isReactive(style) && !isArray(style)) {
style = extend({}, style)
}
props.style = normalizeStyle(style)
}
}
// 通過傳入type得到vnode屬于哪種形式
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT //普通標簽
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isPortal(type)
? ShapeFlags.PORTAL
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT // 有狀態(tài)型組件 下文稱為組件
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT //方法型組件
: 0
const vnode: VNode = {
_isVNode: true,
type,
props,
key: props && props.key !== undefined ? props.key : null,
ref:
props && props.ref !== undefined
? [currentRenderingInstance!, props.ref]
: null,
scopeId: currentScopeId,
children: null,
component: null,
suspense: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
shapeFlag,
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}
//處理Vode的children
normalizeChildren(vnode, children)
// presence of a patch flag indicates this node needs patching on updates.
// component nodes also should always be patched, because even if the
// component doesn't need to update, it needs to persist the instance on to
// the next vnode so that it can be properly unmounted later.
if (
shouldTrack > 0 &&
currentBlock &&
// the EVENTS flag is only for hydration and if it is the only flag, the
// vnode should not be considered dynamic due to handler caching.
patchFlag !== PatchFlags.HYDRATE_EVENTS &&
(patchFlag > 0 ||
shapeFlag & ShapeFlags.SUSPENSE ||
shapeFlag & ShapeFlags.STATEFUL_COMPONENT ||
shapeFlag & ShapeFlags.FUNCTIONAL_COMPONENT)
) {
currentBlock.push(vnode)
}
return vnode
}
由于我們在mout中傳入的是組件
,該vnode只是對其進行了包裝勒葱。
Root的render
const render: RootRenderFunction = (vnode, container) => {
if (vnode == null) {
if (container._vnode) {
unmount(container._vnode, null, null, true)
}
} else {
// 這里到了今天的重點
patch(container._vnode || null, vnode, container)
}
flushPostFlushCbs()
container._vnode = vnode
}
這個邏輯比較簡單:
就是對原_vode和新vnode進行patch操作。
2. patch
const patch: PatchFn = (
n1,
n2,
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) => {
// 發(fā)現VNode類型不相同直接卸載舊VNode
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null //并將n1設置為null巴柿,以便后面處理凛虽。
}
//按照type的類型進行patch
const { type, shapeFlag } = n2
switch (type) {
case Text:
processText(n1, n2, container, anchor)
break
case Comment:
processCommentNode(n1, n2, container, anchor)
break
case Static:
if (n1 == null) {
mountStaticNode(n2, container, anchor, isSVG)
} // static nodes are noop on patch
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) {
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) {
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.PORTAL) {
;(type as typeof PortalImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
internals
)
} else if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
;(type as typeof SuspenseImpl).process(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
internals
)
} else if (__DEV__) {
warn('Invalid VNode type:', type, `(${typeof type})`)
}
}
}
ptachComponent
processComponent與mountComponent
const processComponent = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
if (n1 == null) {
...//此處省去COMPONENT_KEPT_ALIVE的處理
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
}
} else {
const instance = (n2.component = n1.component)!
// 判斷是否需要更新
if (shouldUpdateComponent(n1, n2, parentComponent, optimized)) {
if (
__FEATURE_SUSPENSE__ &&
instance.asyncDep &&
!instance.asyncResolved
) {
updateComponentPreRender(instance, n2)
return
} else {
// normal update
instance.next = n2
// in case the child component is also queued, remove it to avoid
// double updating the same child component in the same flush.
invalidateJob(instance.update)
// instance.update is the reactive effect runner.
instance.update()
}
} else {
// no update needed. just copy over properties
n2.component = n1.component
n2.el = n1.el
}
}
if (n2.ref != null && parentComponent) {
//處理ref
setRef(n2.ref, n1 && n1.ref, parentComponent, n2.component!.proxy)
}
}
上面邏輯比較簡單:
- 就是如果舊的n1不存在直接掛載n2
- 如果n1存在則判斷是否需要更新
2.1 如果需要更新且需要處理異步依賴,則單獨處理直接return
2.2 否則按照通用更新處理:先移除update广恢,然后重新調用update
3.處理ref
接下來凯旋,我們查看mountComponent邏輯。
const mountComponent: MountComponentFn = (
initialVNode,
container,
anchor,
parentComponent,
parentSuspense,
isSVG
) => {
//此處生成組件內部實例
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent
))
...
// 處理props和slots設置到實例
setupComponent(instance, parentSuspense)
...
//又是重點了
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG
)
}
上面我們了解到:
- 創(chuàng)建一個實例
- 處理props和slots钉迷,如何處理我們不探究
- 調用setupRenderEffect
setupRenderEffect做什么
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG
) => {
//創(chuàng)建render的響應式至非,后期補充vue3.0 effect的文章時在了解。現在暫時認為是直接調用了該方法
instance.update = effect(function componentEffect() {
// 當實例沒有掛載的話
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, a, parent } = instance
// 這是重點,這里個里面將會生成Fragment類型VNode
const subTree = (instance.subTree = renderComponentRoot(instance))
... //hock處理beforeMount
if (el && hydrateNode) {
// vnode has adopted host node - perform hydration instead of mount.
hydrateNode(
initialVNode.el as Node,
subTree,
instance,
parentSuspense
)
} else {
// 在調用patch
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
initialVNode.el = subTree.el
}
// mounted hook
...
instance.isMounted = true
} else {
// updateComponent
// This is triggered by mutation of component's own state (next: null)
// OR parent calling processComponent (next: VNode)
let { next, bu, u, parent, vnode } = instance
if (next) {
updateComponentPreRender(instance, next)
} else {
next = vnode
}
// 這是重點,這里個里面將會生成Fragment類型VNode
const nextTree = renderComponentRoot(instance)
const prevTree = instance.subTree
instance.subTree = nextTree
next.el = vnode.el
// reset refs
// only needed if previous patch had refs
if (instance.refs !== EMPTY_OBJ) {
instance.refs = {}
}
// 也是重新patch
patch(
prevTree,
nextTree,
// parent may have changed if it's in a portal
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
next.el = nextTree.el
if (next === null) {
// self-triggered update. In case of HOC, update parent component
// vnode el. HOC is indicated by parent instance's subTree pointing
// to child component's vnode
updateHOCHostEl(instance, nextTree.el)
}
}
}, __DEV__ ? createDevEffectOptions(instance) : prodEffectOptions)
}
setupRenderEffect做了什么:
- 調用renderComponentRoot生成Fragment類型VNodeTree
- 調用patch
renderComponentRoot做什么
export function renderComponentRoot(
instance: ComponentInternalInstance
): VNode {
const {
type: Component,
parent,
vnode,
proxy,
withProxy,
props,
slots,
attrs,
emit,
renderCache
} = instance
let result
currentRenderingInstance = instance
try {
if (vnode.shapeFlag & ShapeFlags.STATEFUL_COMPONENT) {
const proxyToUse = withProxy || proxy
// instance.render 這個就是通過vue-loader將tempalte編譯后生成的render
let vNodeTree = instance.render!.call(proxyToUse, proxyToUse!, renderCache)
result = normalizeVNode(vNodeTree)
} else {
// 處理FunctionalComponent
const render = Component as FunctionalComponent
result = normalizeVNode(
render.length > 1
? render(props, {
attrs,
slots,
emit
})
: render(props, null as any /* we know it doesn't need it */)
)
}
// 合并attr
let fallthroughAttrs
if (
Component.inheritAttrs !== false &&
attrs !== EMPTY_OBJ &&
(fallthroughAttrs = getFallthroughAttrs(attrs))
) {
if (
result.shapeFlag & ShapeFlags.ELEMENT ||
result.shapeFlag & ShapeFlags.COMPONENT
) {
result = cloneVNode(result, fallthroughAttrs)
// If the child root node is a compiler optimized vnode, make sure it
// force update full props to account for the merged attrs.
if (result.dynamicChildren) {
result.patchFlag |= PatchFlags.FULL_PROPS
}
}
}
//繼承 scopeId
const parentScopeId = parent && parent.type.__scopeId
if (parentScopeId) {
result = cloneVNode(result, { [parentScopeId]: '' })
}
// inherit directives
if (vnode.dirs) {
result.dirs = vnode.dirs
}
// inherit transition data
if (vnode.transition) {
result.transition = vnode.transition
}
} catch (err) {
handleError(err, instance, ErrorCodes.RENDER_FUNCTION)
result = createVNode(Comment)
}
currentRenderingInstance = null
return result
}
這里我們知道將會使用:
我們編譯出來的render方法糠聪,通過傳入proxy數據然后生成VNodeTree荒椭。
由于整個patch處理會根據Vnode類型進行不同處理,不過邏輯大致相似舰蟆,所以我下面再選取兩個代表型的Vnode類型趣惠,我們來了解整個diff狸棍。
processElement
const processElement = (
n1: VNode | null,
n2: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
isSVG = isSVG || (n2.type as string) === 'svg'
// 熟悉的邏輯吧
if (n1 == null) {
mountElement(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
patchElement(n1, n2, parentComponent, parentSuspense, isSVG, optimized)
}
if (n2.ref != null && parentComponent) {
setRef(n2.ref, n1 && n1.ref, parentComponent, n2.el)
}
}
來看mountElement做了什么
const mountElement = (
vnode: VNode,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
let el: RendererElement
const {
type,
props,
shapeFlag,
transition,
scopeId,
patchFlag,
dirs
} = vnode
if (
vnode.el &&
hostCloneNode !== undefined &&
patchFlag === PatchFlags.HOISTED
) {
// 對于靜態(tài)VNode直接clone,因為他不受props影響
el = vnode.el = hostCloneNode(vnode.el)
} else {
// hostCreateElement就是document.createElement創(chuàng)建標簽味悄。
el = vnode.el = hostCreateElement(vnode.type as string, isSVG)
// 處理props
if (props) {
for (const key in props) {
if (!isReservedProp(key)) {
// 設置attr
hostPatchProp(el, key, null, props[key], isSVG)
}
}
}
//為標簽設置scopeId
if (scopeId) {
hostSetScopeId(el, scopeId)
}
const treeOwnerId = parentComponent && parentComponent.type.__scopeId
// vnode's own scopeId and the current patched component's scopeId is
// different - this is a slot content node.
if (treeOwnerId && treeOwnerId !== scopeId) {
//設置來自parent的__scopeId
hostSetScopeId(el, treeOwnerId + '-s')
}
//處理children
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(el, vnode.children as string)
} else if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(
vnode.children as VNodeArrayChildren,
el,
null,
parentComponent,
parentSuspense,
isSVG && type !== 'foreignObject',
optimized || !!vnode.dynamicChildren
)
}
if (transition && !transition.persisted) {
transition.beforeEnter(el)
}
}
// 將生成的element掛載到對應位置
hostInsert(el, container, anchor)
}
const patchElement = (
n1: VNode,
n2: VNode,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
const el = (n2.el = n1.el!)
let { patchFlag, dynamicChildren, dirs } = n2
const oldProps = (n1 && n1.props) || EMPTY_OBJ
const newProps = n2.props || EMPTY_OBJ
if (patchFlag > 0) {
if (patchFlag & PatchFlags.FULL_PROPS) {
// element props contain dynamic keys, full diff needed
patchProps(
el,
n2,
oldProps,
newProps,
parentComponent,
parentSuspense,
isSVG
)
} else {
// this flag is matched when the element has dynamic class bindings.
if (patchFlag & PatchFlags.CLASS) {
if (oldProps.class !== newProps.class) {
hostPatchProp(el, 'class', null, newProps.class, isSVG)
}
}
// style
// this flag is matched when the element has dynamic style bindings
if (patchFlag & PatchFlags.STYLE) {
hostPatchProp(el, 'style', oldProps.style, newProps.style, isSVG)
}
// props
// This flag is matched when the element has dynamic prop/attr bindings
// other than class and style. The keys of dynamic prop/attrs are saved for
// faster iteration.
// Note dynamic keys like :[foo]="bar" will cause this optimization to
// bail out and go through a full diff because we need to unset the old key
if (patchFlag & PatchFlags.PROPS) {
// 只考慮動態(tài)的props
const propsToUpdate = n2.dynamicProps!
for (let i = 0; i < propsToUpdate.length; i++) {
const key = propsToUpdate[i]
const prev = oldProps[key]
const next = newProps[key]
if (prev !== next) {
hostPatchProp(
el,
key,
prev,
next,
isSVG,
n1.children as VNode[],
parentComponent,
parentSuspense,
unmountChildren
)
}
}
}
}
// text
// This flag is matched when the element has only dynamic text children.
if (patchFlag & PatchFlags.TEXT) {
if (n1.children !== n2.children) {
hostSetElementText(el, n2.children as string)
}
}
} else if (!optimized && dynamicChildren == null) {
// unoptimized, full diff
patchProps(
el,
n2,
oldProps,
newProps,
parentComponent,
parentSuspense,
isSVG
)
}
const areChildrenSVG = isSVG && n2.type !== 'foreignObject'
if (dynamicChildren) {
patchBlockChildren(
n1.dynamicChildren!,
dynamicChildren,
el,
parentComponent,
parentSuspense,
areChildrenSVG
)
} else if (!optimized) {
// full diff
patchChildren(
n1,
n2,
el,
null,
parentComponent,
parentSuspense,
areChildrenSVG
)
}
if ((vnodeHook = newProps.onVnodeUpdated) || dirs) {
queuePostRenderEffect(() => {
vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, n2, n1)
dirs && invokeDirectiveHook(n2, n1, parentComponent, 'updated')
}, parentSuspense)
}
}
通過上面三個方法我們知道對于element的patch思路是:
- 對于n1為null的,直接生成新的element并掛載到對應位置草戈。
- 對于n1存在的,為了盡可能減少element不必要的屬性修改侍瑟,所以采用每個props進行對比唐片,
- 對于element的Children分別使用mountChildren和patchChildren。
mountChildren
const mountChildren: MountChildrenFn = (
children,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
start = 0
) => {
for (let i = start; i < children.length; i++) {
const child = (children[i] = optimized
? cloneIfMounted(children[i] as VNode)
: normalizeVNode(children[i]))
patch(
null,
child,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
mountChildre比較直接遍歷使用patch
接下來又是一個重點:
const patchChildren: PatchChildrenFn = (
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized = false
) => {
const c1 = n1 && n1.children
const prevShapeFlag = n1 ? n1.shapeFlag : 0
const c2 = n2.children
const { patchFlag, shapeFlag } = n2
if (patchFlag === PatchFlags.BAIL) {
optimized = false
}
// fast path
if (patchFlag > 0) {
// 處理帶key的fragment
if (patchFlag & PatchFlags.KEYED_FRAGMENT) {
// this could be either fully-keyed or mixed (some keyed some not)
// presence of patchFlag means children are guaranteed to be arrays
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
} else if (patchFlag & PatchFlags.UNKEYED_FRAGMENT) {
// 處理不key的fragment
patchUnkeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
return
}
//注意兩種fragment處理完直接return了
}
// children 有三種可能: text, array or no children.
if (shapeFlag & ShapeFlags.TEXT_CHILDREN) {
// text children fast path
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// 移除內容
unmountChildren(c1 as VNode[], parentComponent, parentSuspense)
}
if (c2 !== c1) {
// 直接修改內容
hostSetElementText(container, c2 as string)
}
} else {
if (prevShapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// prev children was array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
// 對于array形式的調用patchKeyedChildren
patchKeyedChildren(
c1 as VNode[],
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
// 新的children為空,則直接卸載
unmountChildren(c1 as VNode[], parentComponent, parentSuspense, true)
}
} else {
// prev children was text OR null
// new children is array OR null
if (prevShapeFlag & ShapeFlags.TEXT_CHILDREN) {
hostSetElementText(container, '')
}
// mount new if array
if (shapeFlag & ShapeFlags.ARRAY_CHILDREN) {
mountChildren(
c2 as VNodeArrayChildren,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
}
}
}
先通過類型進行處理:
其核心思路是:將有key和無key單獨處理
下面是無key的處理
const patchUnkeyedChildren = (
c1: VNode[],
c2: VNodeArrayChildren,
container: RendererElement,
anchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
c1 = c1 || EMPTY_ARR
c2 = c2 || EMPTY_ARR
const oldLength = c1.length
const newLength = c2.length
const commonLength = Math.min(oldLength, newLength)
let i
for (i = 0; i < commonLength; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
patch(
c1[i],
nextChild,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
if (oldLength > newLength) {
// remove old
unmountChildren(c1, parentComponent, parentSuspense, true, commonLength)
} else {
// mount new
mountChildren(
c2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized,
commonLength
)
}
}
無key的邏輯比較簡單:
- 直接遍歷n2與n1最小長度:使用patch處理
- n1多出來的:unmountChildren
- n2多出來的:進行mountChildren
重點來了涨颜,有key比較復雜费韭。
// can be all-keyed or mixed
const patchKeyedChildren = (
c1: VNode[],
c2: VNodeArrayChildren,
container: RendererElement,
parentAnchor: RendererNode | null,
parentComponent: ComponentInternalInstance | null,
parentSuspense: SuspenseBoundary | null,
isSVG: boolean,
optimized: boolean
) => {
let i = 0
const l2 = c2.length
let e1 = c1.length - 1 // prev ending index
let e2 = l2 - 1 // next ending index
// 1.先從開頭遍歷,發(fā)現不一致則停止
//如下形式:只遍歷到ab
// (a b) c
// (a b) d e
while (i <= e1 && i <= e2) {
const n1 = c1[i]
const n2 = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if (isSameVNodeType(n1, n2)) {
patch(
n1,
n2,
container,
parentAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
break
}
i++
}
// 2. 接著從后面開始倒著遍歷咐低,發(fā)現類型不一致就停止揽思。
// a (b c)
// d e (b c)
while (i <= e1 && i <= e2) {
const n1 = c1[e1]
const n2 = (c2[e2] = optimized
? cloneIfMounted(c2[e2] as VNode)
: normalizeVNode(c2[e2]))
if (isSameVNodeType(n1, n2)) {
patch(
n1,
n2,
container,
parentAnchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else {
break
}
e1--
e2--
}
// 3. 處理如下形式:即n2在兩頭多出來的,進行patch(mount)
// (a b)
// (a b) c
// i = 2, e1 = 1, e2 = 2
// (a b)
// c (a b)
// i = 0, e1 = -1, e2 = 0
if (i > e1) {
if (i <= e2) {
const nextPos = e2 + 1
const anchor = nextPos < l2 ? (c2[nextPos] as VNode).el : parentAnchor
while (i <= e2) {
patch(
null,
(c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i])),
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
i++
}
}
}
// 4. 處理如下形式:即n1在兩頭多出來的见擦,進行刪除
// (a b) c
// (a b)
// i = 2, e1 = 2, e2 = 1
// a (b c)
// (b c)
// i = 0, e1 = 0, e2 = -1
else if (i > e2) {
while (i <= e1) {
unmount(c1[i], parentComponent, parentSuspense, true)
i++
}
}
// 5. 接下來處理中間部分,
// [i ... e1 + 1]: a b [c d e] f g
// [i ... e2 + 1]: a b [e d c h] f g
// i = 2, e1 = 4, e2 = 5
else {
const s1 = i // prev starting index
const s2 = i // next starting index
// 由于中間部分不好判斷是否有增加或者刪除導致的不一致,還是全部替換了
// 所以采取如下策略
// 5.1 對于n2的中間部分遍歷羹令,并將childVnode與key存入Map中
const keyToNewIndexMap: Map<string | number, number> = new Map()
for (i = s2; i <= e2; i++) {
const nextChild = (c2[i] = optimized
? cloneIfMounted(c2[i] as VNode)
: normalizeVNode(c2[i]))
if (nextChild.key != null) {
keyToNewIndexMap.set(nextChild.key, i)
}
}
// 5.2 loop through old children left to be patched and try to patch
// matching nodes & remove nodes that are no longer present
let j
let patched = 0
const toBePatched = e2 - s2 + 1 //n2中間的長度
let moved = false
// used to track whether any node has moved
let maxNewIndexSoFar = 0
// works as Map<newIndex, oldIndex>
// Note that oldIndex is offset by +1
// and oldIndex = 0 is a special value indicating the new node has
// no corresponding old node.
// used for determining longest stable subsequence
const newIndexToOldIndexMap = new Array(toBePatched)
for (i = 0; i < toBePatched; i++) newIndexToOldIndexMap[i] = 0
//遍歷n1中間的
for (i = s1; i <= e1; i++) {
const prevChild = c1[i]
//如果n2中的已經全部被處理鲤屡,則直接刪除
// n1 中間的[c d e j k h z w]
// n2 中間的[e d c h]
// 如上形式會刪除 z w
if (patched >= toBePatched) {
// all new children have been patched so this can only be a removal
unmount(prevChild, parentComponent, parentSuspense, true)
continue
}
//此處獲取到n1遍歷的prevChild對應的到n2中的newIndex
let newIndex
if (prevChild.key != null) {
newIndex = keyToNewIndexMap.get(prevChild.key)
} else {
// key-less node, try to locate a key-less node of the same type
for (j = s2; j <= e2; j++) {
if (
newIndexToOldIndexMap[j - s2] === 0 &&
isSameVNodeType(prevChild, c2[j] as VNode)
) {
newIndex = j
break
}
}
}
// newIndex不存在,即n2中沒找到匹配的,preChild應該刪除
// n1 中間的[c d e j k h]
// n2 中間的[e d c h]
// 如上形式會刪除 j k
if (newIndex === undefined) {
unmount(prevChild, parentComponent, parentSuspense, true)
} else {
//對于匹配到的進行patch
newIndexToOldIndexMap[newIndex - s2] = i + 1
if (newIndex >= maxNewIndexSoFar) {
maxNewIndexSoFar = newIndex
} else {
moved = true
}
patch(
prevChild,
c2[newIndex] as VNode,
container,
null,
parentComponent,
parentSuspense,
isSVG,
optimized
)
patched++
}
}
// 5.3 move and mount
// n1 中間的[c d e]
// n2 中間的[e d c h]
//如上 需要對多處來的h進行mount
// e d c 則需要移動位置
// i = 2, e1 = 4, e2 = 5
// generate longest stable subsequence only when nodes have moved
const increasingNewIndexSequence = moved
? getSequence(newIndexToOldIndexMap)
: EMPTY_ARR
j = increasingNewIndexSequence.length - 1
// looping backwards so that we can use last patched node as anchor
for (i = toBePatched - 1; i >= 0; i--) {
const nextIndex = s2 + i
const nextChild = c2[nextIndex] as VNode
const anchor =
nextIndex + 1 < l2 ? (c2[nextIndex + 1] as VNode).el : parentAnchor
if (newIndexToOldIndexMap[i] === 0) {
// mount new
patch(
null,
nextChild,
container,
anchor,
parentComponent,
parentSuspense,
isSVG
)
} else if (moved) {
// move if:
// There is no stable subsequence (e.g. a reverse)
// OR current node is not among the stable sequence
if (j < 0 || i !== increasingNewIndexSequence[j]) {
move(nextChild, container, anchor, MoveType.REORDER)
} else {
j--
}
}
}
}
}
好了福侈,至此diff的算法完成了酒来。
總結下patch時diff的處理的
簡單來說就是:n1 代表舊節(jié)點,n2代表新節(jié)點
- 如果是單VNode對比:
1.1 n1為空肪凛,則創(chuàng)建n2
1.2 n2為空堰汉,則刪除n1
1.3 n1的類型與n2不一致,則 刪除n1創(chuàng)建n2.
1.4 n1與n2一致伟墙,則patch翘鸭,判斷其patch其props,class戳葵,attr就乓,進行dom操作 - 如果是多節(jié)點,且是無key形式
2.1 取n2與n1最小長度遍歷:使用patch處理
2.2 如果n1多出來的:unmountChildren
2.3 如果n2多出來的:進行mountChildren - 如果是多節(jié)點拱烁,有key形式
3.1 先從開頭遍歷生蚁,類型一致則patch,發(fā)現類型不一致則停止戏自。如(a b) c
與(a b) d e
3.2 從尾開始遍歷邦投,類型一致則patch,發(fā)現類型不一致則停止擅笔。 如a c (d f)
與b a (d f)
3.3 處理n2兩頭多出來的部分志衣, 直接mount见芹。如b c
與(a) b c
或者b c
與b c (d)
3.4 處理n1兩頭多出來的部分, 直接刪除蠢涝。如(a) b c
與b c
或者b c (d)
與b c
3.5 處理n1與n2 中間遺留的如b (e f g h) c
與b (f g k h) c
玄呛。
3.5.1 將n2中間的f g k h
生成Map
3.5.2 遍歷n1中間的(e f g h)
3.5.2.1 遍歷過程中發(fā)現不在n2中,則直接刪除和二。
3.5.2.2 遍歷過程中發(fā)現在n2中徘铝,則直接patch。并記錄n2中位置惯吕。后面需要移動位置惕它。
3.6 對于n2中間的f g k h
新增的如k
進行mount。
3.7 對于3.5.2.2中的進行移動位置废登。
我們就知道了,如果有key
的話淹魄,會通過key篩選
,從而明確哪個節(jié)點刪除/替換/移動
盡可能的減少dom操作
堡距。但是沒有key
的話甲锡,只能通過遍歷,相對而言這種操作dom
的情況較多羽戒。
比如:a b c d e f
與 b c d e f
這種形式會導致缤沦,每個節(jié)點都要進行調整操作dom。
看來真的不是簡單來說易稠,應該是復雜來說缸废。。驶社。
總結下如何渲染出來內容
由于vue-loader企量,在編譯過程中已經幫我們將template轉成了render函數。并將.vue編譯成object形式亡电。
- 在createApp(rootComponent).mount(el)的時候調用vue的render届巩。
- 會將object形式的rootComponent,轉換成type為State_Component的VNode_Component逊抡。
- vue的render會調用patch(VNode_Component)姆泻。
- 當patch 發(fā)現時component,則會調用其實例的render(即template編譯后生成的render方法)來生成fragment類型的Vnode冒嫡,對其patch拇勃。
- 然后就開啟了套娃模式,一層層patch孝凌。
- 最終調用到mountElement(vNode)形式方咆,去使用document.createElement創(chuàng)建dom節(jié)點。