前面两个章,我们知道到了.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(
render: RootRenderFunction,
hydrate?: RootHydrateFunction
): CreateAppFunction {
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创建VNode
const vnode = createVNode(rootComponent as Component, rootProps)
vnode.appContext = context
//渲染
if (isHydrate && hydrate) {
hydrate(vnode as VNode, 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组件
创建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 // 有状态型组件 下文称为组件
: 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
) => {
// 发现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
)
}
上面我们了解到:
- 创建一个实例
- 处理props和slots,如何处理我们不探究
- 调用setupRenderEffect
setupRenderEffect做什么
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG
) => {
//创建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
) {
// 对于静态VNode直接clone,因为他不受props影响
el = vnode.el = hostCloneNode(vnode.el)
} else {
// hostCreateElement就是document.createElement创建标签。
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) {
// 只考虑动态的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.先从开头遍历,发现不一致则停止
//如下形式:只遍历到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. 接着从后面开始倒着遍历,发现类型不一致就停止。
// 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 = 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
// 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 代表旧节点,n2代表新节点
- 如果是单VNode对比:
1.1 n1为空,则创建n2
1.2 n2为空,则删除n1
1.3 n1的类型与n2不一致,则 删除n1创建n2.
1.4 n1与n2一致,则patch,判断其patch其props,class,attr,进行dom操作 - 如果是多节点,且是无key形式
2.1 取n2与n1最小长度遍历:使用patch处理
2.2 如果n1多出来的:unmountChildren
2.3 如果n2多出来的:进行mountChildren - 如果是多节点,有key形式
3.1 先从开头遍历,类型一致则patch,发现类型不一致则停止。如(a b) c
与(a b) d e
3.2 从尾开始遍历,类型一致则patch,发现类型不一致则停止。 如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 遍历过程中发现不在n2中,则直接删除。
3.5.2.2 遍历过程中发现在n2中,则直接patch。并记录n2中位置。后面需要移动位置。
3.6 对于n2中间的f g k h
新增的如k
进行mount。
3.7 对于3.5.2.2中的进行移动位置。
我们就知道了,如果有key
的话,会通过key筛选
,从而明确哪个节点删除/替换/移动
尽可能的减少dom操作
。但是没有key
的话,只能通过遍历,相对而言这种操作dom
的情况较多。
比如:a b c d e f
与 b c d e f
这种形式会导致,每个节点都要进行调整操作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 发现时component,则会调用其实例的render(即template编译后生成的render方法)来生成fragment类型的Vnode,对其patch。
- 然后就开启了套娃模式,一层层patch。
- 最终调用到mountElement(vNode)形式,去使用document.createElement创建dom节点。