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