# 渲染器
渲染器的作用是将虚拟DOM
渲染为真实DOM。
# VNode
# VNode的类型
常规的VNode
包括Text
、Comment
、Static
、Fragment
这几种类型。
// 所有VNode类型
export type VNodeTypes =
| string
| VNode
| Component
| typeof Text
| typeof Static
| typeof Comment
| typeof Fragment
| typeof TeleportImpl
| typeof SuspenseImpl
export const Text = Symbol(__DEV__ ? 'Text' : undefined) // 纯文本
export const Comment = Symbol(__DEV__ ? 'Comment' : undefined) // 注释类
export const Static = Symbol(__DEV__ ? 'Static' : undefined) // 静态html
export const Fragment = (Symbol(__DEV__ ? 'Fragment' : undefined) as any) as {
__isFragment: true
new (): {
$props: VNodeProps
}
} // fragment html片段,根节点是多个节点
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
VNode
的内容:
const vnode: VNode = {
__v_isVNode: true,
[ReactiveFlags.SKIP]: true,
type,
props,
key: props && normalizeKey(props),
ref: props && normalizeRef(props),
scopeId: currentScopeId,
children: null,
component: null,
suspense: null,
ssContent: null,
ssFallback: null,
dirs: null,
transition: null,
el: null,
anchor: null,
target: null,
targetAnchor: null,
staticCount: 0,
shapeFlag, // 用于
patchFlag,
dynamicProps,
dynamicChildren: null,
appContext: null
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
createVNode
位于@vue/runtime-core/vnode.ts
,在生产环境调用了第317行的_createVNode
,其代码如下:
function _createVNode(
type: VNodeTypes | ClassComponent | typeof NULL_DYNAMIC_COMPONENT,
props: (Data & VNodeProps) | null = null,
children: unknown = null,
patchFlag: number = 0,
dynamicProps: string[] | null = null,
isBlockNode = false
): VNode {
if (!type || type === NULL_DYNAMIC_COMPONENT) {
type = Comment
}
if (isVNode(type)) {
// createVNode receiving an existing vnode. This happens in cases like
// <component :is="vnode"/>
// #2078 make sure to merge refs during the clone instead of overwriting it
const cloned = cloneVNode(type, props, true /* mergeRef: true */)
if (children) {
normalizeChildren(cloned, children)
}
return cloned
}
// class component normalization.
if (isClassComponent(type)) {
type = type.__vccOpts
}
// class & style normalization.
if (props) {
// for reactive or proxy objects, we need to clone it to enable mutation.
if (isProxy(props) || InternalObjectKey in props) {
props = extend({}, props)
}
let { class: klass, style } = props
if (klass && !isString(klass)) {
props.class = normalizeClass(klass)
}
if (isObject(style)) {
// reactive state objects need to be cloned since they are likely to be
// mutated
if (isProxy(style) && !isArray(style)) {
style = extend({}, style)
}
props.style = normalizeStyle(style)
}
}
// encode the vnode type information into a bitmap
const shapeFlag = isString(type)
? ShapeFlags.ELEMENT
: __FEATURE_SUSPENSE__ && isSuspense(type)
? ShapeFlags.SUSPENSE
: isTeleport(type)
? ShapeFlags.TELEPORT
: isObject(type)
? ShapeFlags.STATEFUL_COMPONENT
: isFunction(type)
? ShapeFlags.FUNCTIONAL_COMPONENT
: 0
const vnode: VNode = {
// ... 省略VNode内容
}
normalizeChildren(vnode, children)
// normalize suspense children
if (__FEATURE_SUSPENSE__ && shapeFlag & ShapeFlags.SUSPENSE) {
const { content, fallback } = normalizeSuspenseChildren(vnode)
vnode.ssContent = content
vnode.ssFallback = fallback
}
if (
shouldTrack > 0 &&
// avoid a block node from tracking itself
!isBlockNode &&
// has current parent block
currentBlock &&
// 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.
(patchFlag > 0 || shapeFlag & ShapeFlags.COMPONENT) &&
// 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
) {
currentBlock.push(vnode)
}
return vnode
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
# 渲染器的基本结构
在Vue3
中渲染器被抽象成了一个对象,其TypeScript
类型定义位于@vue/runtime-core/renderer.ts
中:
export interface Renderer<HostElement = RendererElement> {
render: RootRenderFunction<HostElement>
createApp: CreateAppFunction<HostElement>
}
2
3
4
主要就是一个拥有render
和createApp
方法的对象。
在Vue3
中可以通过createRenderer
自定义渲染器,浏览器平台的DOM
渲染器就是自定义渲染器的一个实现。
export function createRenderer<
HostNode = RendererNode,
HostElement = RendererElement
>(options: RendererOptions<HostNode, HostElement>) {
return baseCreateRenderer<HostNode, HostElement>(options)
}
2
3
4
5
6
createRenderer
函数将自定义渲染器的依赖抽象成了RendererOptions
,其TypeScript
类型定义如下:
export interface RendererOptions<
HostNode = RendererNode,
HostElement = RendererElement
> {
patchProp(
el: HostElement,
key: string,
prevValue: any,
nextValue: any,
isSVG?: boolean,
prevChildren?: VNode<HostNode, HostElement>[],
parentComponent?: ComponentInternalInstance | null,
parentSuspense?: SuspenseBoundary | null,
unmountChildren?: UnmountChildrenFn
): void
insert(el: HostNode, parent: HostElement, anchor?: HostNode | null): void
remove(el: HostNode): void
createElement(
type: string,
isSVG?: boolean,
isCustomizedBuiltIn?: string,
vnodeProps?: (VNodeProps & { [key: string]: any }) | null
): HostElement
createText(text: string): HostNode
createComment(text: string): HostNode
setText(node: HostNode, text: string): void
setElementText(node: HostElement, text: string): void
parentNode(node: HostNode): HostElement | null
nextSibling(node: HostNode): HostNode | null
querySelector?(selector: string): HostElement | null
setScopeId?(el: HostElement, id: string): void
cloneNode?(node: HostNode): HostNode
insertStaticContent?(
content: string,
parent: HostElement,
anchor: HostNode | null,
isSVG: boolean,
start?: HostNode | null,
end?: HostNode | null
): [HostNode, HostNode]
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
# mount
app
的mount
方法中的render
由createAppAPI
作为参数传入,来源于@vue/runtime-core/renderer.ts
中的baseCreateRenderer
方法定义:
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() // TODO 待解析
container._vnode = vnode
}
2
3
4
5
6
7
8
9
10
11
render
方法主要有两个逻辑分支,如果vnode
不为null
时,为新建或者更新逻辑,调用patch
方法;如果vnode
为null
,,并且container._vnode
不为null
,即vnode
从有值变为null
,调用unmount
方法。
# patch
patch
函数是整个渲染器的核心入口,承载了最重要的渲染逻辑
# patch的类型
不同类型的元素或组件,有不同的patch策略
export const enum PatchFlags {
// 表明元素具有动态文本
TEXT = 1,
// 表明元素具有动态class绑定
CLASS = 1 << 1, // 2
// 表明元素具有动态样式style,编译器会将静态对象字符串编译为静态对象,并做变量提升优化
// 例如:
// style="color: red" 或 :style="{ color: 'red' }"
// 将被编译为:
// const style = { color: 'red' }
// render() { return e('div', { style }) }
STYLE = 1 << 2, // 4
// 表明元素或者组件具有class/style以外的动态属性props
// Can also be on a component that has any dynamic props (includes
// class/style). when this flag is present, the vnode also has a dynamicProps
// array that contains the keys of the props that may change so the runtime
// can diff them faster (without having to worry about removed props)
PROPS = 1 << 3, // 8
// Indicates an element with props with dynamic keys. When keys change, a full
// diff is always needed to remove the old key. This flag is mutually
// exclusive with CLASS, STYLE and PROPS.
FULL_PROPS = 1 << 4,
// Indicates an element with event listeners (which need to be attached
// during hydration)
HYDRATE_EVENTS = 1 << 5,
// Indicates a fragment whose children order doesn't change.
STABLE_FRAGMENT = 1 << 6,
// Indicates a fragment with keyed or partially keyed children
KEYED_FRAGMENT = 1 << 7,
// Indicates a fragment with unkeyed children.
UNKEYED_FRAGMENT = 1 << 8,
// Indicates an element that only needs non-props patching, e.g. ref or
// directives (onVnodeXXX hooks). since every patched vnode checks for refs
// and onVnodeXXX hooks, it simply marks the vnode so that a parent block
// will track it.
NEED_PATCH = 1 << 9,
// Indicates a component with dynamic slots (e.g. slot that references a v-for
// iterated value, or dynamic slot names).
// Components with this flag are always force updated.
DYNAMIC_SLOTS = 1 << 10,
// SPECIAL FLAGS -------------------------------------------------------------
// Special flags are negative integers. They are never matched against using
// bitwise operators (bitwise matching should only happen in branches where
// patchFlag > 0), and are mutually exclusive. When checking for a special
// flag, simply check patchFlag === FLAG.
// Indicates a hoisted static vnode. This is a hint for hydration to skip
// the entire sub tree since static content never needs to be updated.
HOISTED = -1,
// A special flag that indicates that the diffing algorithm should bail out
// of optimized mode. For example, on block fragments created by renderSlot()
// when encountering non-compiler generated slots (i.e. manually written
// render functions, which should always be fully diffed)
// OR manually cloneVNodes
BAIL = -2
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
patch
方法位于同文件的452行:
const patch: PatchFn = (
n1, // prev vnode
n2, // current vnode
container,
anchor = null,
parentComponent = null,
parentSuspense = null,
isSVG = false,
optimized = false
) => {
// patching & not same type, unmount old tree
if (n1 && !isSameVNodeType(n1, n2)) {
anchor = getNextHostNode(n1)
unmount(n1, parentComponent, parentSuspense, true)
n1 = null
}
if (n2.patchFlag === PatchFlags.BAIL) {
optimized = false
n2.dynamicChildren = null
}
const { type, ref, 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)
} else if (__DEV__) {
patchStaticNode(n1, n2, container, isSVG)
}
break
case Fragment:
processFragment(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
break
default:
if (shapeFlag & ShapeFlags.ELEMENT) { // VNode 是普通标签
processElement(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.COMPONENT) { // VNode 是组件
processComponent(
n1,
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
} else if (shapeFlag & ShapeFlags.TELEPORT) {
;(type as typeof TeleportImpl).process(
n1 as TeleportVNode,
n2 as TeleportVNode,
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
)
}
}
// set ref
if (ref != null && parentComponent) {
setRef(ref, n1 && n1.ref, parentComponent, parentSuspense, n2)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
patch
方法经过swtich
语句调用了processComponent
方法,其代码位于同文件的第1201行:
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) {
if (n2.shapeFlag & ShapeFlags.COMPONENT_KEPT_ALIVE) {
(parentComponent!.ctx as KeepAliveContext).activate(
n2,
container,
anchor,
isSVG,
optimized
)
} else {
mountComponent(
n2,
container,
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
)
}
} else {
updateComponent(n1, n2, optimized)
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
其主要有两个逻辑分支:旧的VNode``n1
不为null
时,调用更新组件的updateComponent
方法;当n1
为null
时,又有两个子分支:新的VNode``n1
为keep-alive
的组件时,调用其ctx
的activate
方法,否则调用挂载组件的mountComponent
方法。
我们先看挂载组件的mountComponent
方法,其位于processComponent
的下方:
const mountComponent: MountComponentFn = (
initialVNode, // 初始的VNode
container, // 挂载的容器
anchor,
parentComponent,
parentSuspense,
isSVG,
optimized
) => {
const instance: ComponentInternalInstance = (initialVNode.component = createComponentInstance(
initialVNode,
parentComponent,
parentSuspense
))
// inject renderer internals for keepAlive
if (isKeepAlive(initialVNode)) {
(instance.ctx as KeepAliveContext).renderer = internals
}
setupComponent(instance)
// setup() is async. This component relies on async logic to be resolved
// before proceeding
if (__FEATURE_SUSPENSE__ && instance.asyncDep) {
parentSuspense && parentSuspense.registerDep(instance, setupRenderEffect)
// Give it a placeholder if this is not hydration
// TODO handle self-defined fallback
if (!initialVNode.el) {
const placeholder = (instance.subTree = createVNode(Comment))
processCommentNode(null, placeholder, container!, anchor)
}
return
}
setupRenderEffect(
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
# create
首先调用createComponentInstance
方法生成组件的实例,先看下组件实例的构成:
const instance: ComponentInternalInstance = {
uid: uid++,
vnode,
type,
parent,
appContext,
root: null!, // to be immediately set
next: null,
subTree: null!, // will be set synchronously right after creation
update: null!, // will be set synchronously right after creation
render: null,
proxy: null,
exposed: null,
withProxy: null,
effects: null,
provides: parent ? parent.provides : Object.create(appContext.provides),
accessCache: null!,
renderCache: [],
// local resovled assets
components: null,
directives: null,
// resolved props and emits options
propsOptions: normalizePropsOptions(type, appContext),
emitsOptions: normalizeEmitsOptions(type, appContext),
// emit
emit: null as any, // to be set immediately
emitted: null,
// state
ctx: EMPTY_OBJ,
data: EMPTY_OBJ,
props: EMPTY_OBJ,
attrs: EMPTY_OBJ,
slots: EMPTY_OBJ,
refs: EMPTY_OBJ,
setupState: EMPTY_OBJ,
setupContext: null,
// suspense related
suspense,
suspenseId: suspense ? suspense.pendingId : 0,
asyncDep: null,
asyncResolved: false,
// lifecycle hooks
// not using enums here because it results in computed properties
isMounted: false,
isUnmounted: false,
isDeactivated: false,
bc: null,
c: null,
bm: null,
m: null,
bu: null,
u: null,
um: null,
bum: null,
da: null,
a: null,
rtg: null,
rtc: null,
ec: null
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
createComponentInstance
方法的代码位于@vue/runtime-core/component.ts
的第401行:
export function createComponentInstance(
vnode: VNode,
parent: ComponentInternalInstance | null,
suspense: SuspenseBoundary | null
) {
const type = vnode.type as ConcreteComponent
// inherit parent app context - or - if root, adopt from root vnode
const appContext =
(parent ? parent.appContext : vnode.appContext) || emptyAppContext
const instance: ComponentInternalInstance = {
// 省略componentInstance内容
}
if (__DEV__) { // TODO
instance.ctx = createRenderContext(instance)
} else {
instance.ctx = { _: instance }
}
instance.root = parent ? parent.root : instance
instance.emit = emit.bind(null, instance)
return instance
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
# setup
setup
主要包括setupComponent
和setupRenderEffect
两个过程。
setupComponent
方法代码位于@vue/runtime-core/component.ts
的第516行:
export let isInSSRComponentSetup = false // 表明组件是否服务端渲染的变量
export function setupComponent(
instance: ComponentInternalInstance,
isSSR = false
) {
isInSSRComponentSetup = isSSR
const { props, children, shapeFlag } = instance.vnode
const isStateful = shapeFlag & ShapeFlags.STATEFUL_COMPONENT
initProps(instance, props, isStateful, isSSR)
initSlots(instance, children)
const setupResult = isStateful
? setupStatefulComponent(instance, isSSR)
: undefined
isInSSRComponentSetup = false
return setupResult
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
内部主要做了初始化属性initProps
和初始化插槽initSlots
这两件事儿,并且带状态isStateful
的组件,会执行setupStatefulComponent
方法获取setupResult
。最后将标识组件是否在SSR
环境下执行setup
的标识置为false
,并返回setupResult
。
initProps
方法位于@vue/runtime-core/componentProps.ts
的第114行:
export function initProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
isStateful: number, // result of bitwise flag comparison
isSSR = false
) {
const props: Data = {}
const attrs: Data = {}
def(attrs, InternalObjectKey, 1)
setFullProps(instance, rawProps, props, attrs)
// validation
if (__DEV__) {
validateProps(props, instance)
}
if (isStateful) {
// stateful
instance.props = isSSR ? props : shallowReactive(props)
} else {
if (!instance.type.props) {
// functional w/ optional props, props === attrs
instance.props = attrs
} else {
// functional w/ declared props
instance.props = props
}
}
instance.attrs = attrs
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
setFullProps
位于同文件:
function setFullProps(
instance: ComponentInternalInstance,
rawProps: Data | null,
props: Data,
attrs: Data
) {
const [options, needCastKeys] = instance.propsOptions
if (rawProps) {
for (const key in rawProps) {
const value = rawProps[key]
// key, ref are reserved and never passed down
if (isReservedProp(key)) {
continue
}
// prop option names are camelized during normalization, so to support
// kebab -> camel conversion here we need to camelize the key.
let camelKey
if (options && hasOwn(options, (camelKey = camelize(key)))) {
props[camelKey] = value
} else if (!isEmitListener(instance.emitsOptions, key)) {
// Any non-declared (either as a prop or an emitted event) props are put
// into a separate `attrs` object for spreading. Make sure to preserve
// original key casing
attrs[key] = value
}
}
}
if (needCastKeys) {
const rawCurrentProps = toRaw(props)
for (let i = 0; i < needCastKeys.length; i++) {
const key = needCastKeys[i]
props[key] = resolvePropValue(
options!,
rawCurrentProps,
key,
rawCurrentProps[key],
instance
)
}
}
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
shallowReactive
为Vue3
暴露的响应式核心API
之一,用于创建一个不会深度嵌套的响应式proxy
。调用了Reactivity
部分的核心代码createReactiveObject
,通过new Proxy
的方式来响应data
的变化。
# update
setupRenderEffect
主要设置了实例的update
方法,此处调用了reactivity
中的effect
方法,在定义之后会立即执行,出发DOM
渲染:
const setupRenderEffect: SetupRenderEffectFn = (
instance,
initialVNode,
container,
anchor,
parentSuspense,
isSVG,
optimized
) => {
// create reactive effect for rendering
instance.update = effect(function componentEffect() {
if (!instance.isMounted) {
let vnodeHook: VNodeHook | null | undefined
const { el, props } = initialVNode
const { bm, m, parent } = instance
// beforeMount hook
if (bm) {
invokeArrayFns(bm)
}
// onVnodeBeforeMount
if ((vnodeHook = props && props.onVnodeBeforeMount)) {
invokeVNodeHook(vnodeHook, parent, initialVNode)
}
const subTree = (instance.subTree = renderComponentRoot(instance))
if (__DEV__) {
endMeasure(instance, `render`)
}
if (el && hydrateNode) {
if (__DEV__) {
startMeasure(instance, `hydrate`)
}
// vnode has adopted host node - perform hydration instead of mount.
hydrateNode(
initialVNode.el as Node,
subTree,
instance,
parentSuspense
)
if (__DEV__) {
endMeasure(instance, `hydrate`)
}
} else {
if (__DEV__) {
startMeasure(instance, `patch`)
}
patch(
null,
subTree,
container,
anchor,
instance,
parentSuspense,
isSVG
)
if (__DEV__) {
endMeasure(instance, `patch`)
}
initialVNode.el = subTree.el
}
// mounted hook
if (m) {
queuePostRenderEffect(m, parentSuspense)
}
// onVnodeMounted
if ((vnodeHook = props && props.onVnodeMounted)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook!, parent, initialVNode)
}, parentSuspense)
}
// activated hook for keep-alive roots.
// #1742 activated hook must be accessed after first render
// since the hook may be injected by a child keep-alive
const { a } = instance
if (
a &&
initialVNode.shapeFlag & ShapeFlags.COMPONENT_SHOULD_KEEP_ALIVE
) {
queuePostRenderEffect(a, parentSuspense)
}
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
let originNext = next
let vnodeHook: VNodeHook | null | undefined
if (__DEV__) {
pushWarningContext(next || instance.vnode)
}
if (next) {
updateComponentPreRender(instance, next, optimized)
} else {
next = vnode
}
next.el = vnode.el
// beforeUpdate hook
if (bu) {
invokeArrayFns(bu)
}
// onVnodeBeforeUpdate
if ((vnodeHook = next.props && next.props.onVnodeBeforeUpdate)) {
invokeVNodeHook(vnodeHook, parent, next, vnode)
}
const nextTree = renderComponentRoot(instance)
if (__DEV__) {
endMeasure(instance, `render`)
}
const prevTree = instance.subTree
instance.subTree = nextTree
// reset refs
// only needed if previous patch had refs
if (instance.refs !== EMPTY_OBJ) {
instance.refs = {}
}
if (__DEV__) {
startMeasure(instance, `patch`)
}
patch(
prevTree,
nextTree,
// parent may have changed if it's in a teleport
hostParentNode(prevTree.el!)!,
// anchor may have changed if it's in a fragment
getNextHostNode(prevTree),
instance,
parentSuspense,
isSVG
)
next.el = nextTree.el
if (originNext === 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)
}
// updated hook
if (u) {
queuePostRenderEffect(u, parentSuspense)
}
// onVnodeUpdated
if ((vnodeHook = next.props && next.props.onVnodeUpdated)) {
queuePostRenderEffect(() => {
invokeVNodeHook(vnodeHook!, parent, next!, vnode)
}, parentSuspense)
}
}
}, prodEffectOptions)
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159