Jinuss's blog Jinuss's blog
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)

东流

Web、WebGIS技术博客
首页
  • 源码合集

    • Leaflet源码分析
    • Openlayers源码合集
    • vue3源码
  • HTML
  • CSS
  • 技术文档
  • GitHub技巧
  • 学习
  • 实用技巧
关于
  • 分类
  • 标签
  • 归档
GitHub (opens new window)
  • reactivity响应式

  • runtime-core运行时核心模块

    • watch和watcherEffect源码解析
    • scheduler调度器
    • directive自定义指令实现原理
    • provide依赖和inject注入详解
    • 生命周期函数Lifecycle解析
    • 渲染器renderer源码解析
    • patch方法详解
    • patch中组件的挂载解析
    • patch中组件的更新解析
    • patch中DOM元素的挂载解析
      • 概览
      • 源码解析
      • 辅助方法
        • mountChildren
        • cloneIfMounted
        • cloneVNode
        • normalizeVNode
        • createVNode
        • setScopeId
    • patchDOM元素的更新解析
    • patch中的双端比较快速算法
  • runtime-dom运行时DOM模块

  • 5.18源码学习》
  • runtime-core运行时核心模块
东流
2025-09-25
目录

patch中DOM元素的挂载解析

# 概览

在patch方法中,若新虚拟节点n2的shapeFlag判断是DOM元素,则会调用processElement方法处理元素的更新或挂载,若旧虚拟节点n1为null,则会调用mountElement挂载DOM元素。

mountElement方法是vue3中挂载元素节点的核心函数,它负责将虚拟节点VNode转换为真实的DOM元素并插入到容器中。

# 源码解析

  • 参数介绍

mountElement方法的参数说明如下:

  • vnode:要挂载的虚拟节点

  • container:挂载的容器元素

  • anchor:插入的参考位置锚点,新元素会插入到这个锚点之前

  • parentComponent:父组件实例

  • parentSuspense:父级Suspense实例

  • namespace:命名空间,如SVG

  • slotScopeIds:插槽作用域ID ,可以用于优化

  • optimized:是否处于优化模式,用于静态节点优化

  • 源码分析

const mountElement = (vnode, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
  let el;
  let vnodeHook;
  const { props, shapeFlag, transition, dirs } = vnode;
  // 创建真实DOM元素
  el = vnode.el = hostCreateElement(
    vnode.type,
    namespace,
    props && props.is,
    props
  );
  // 处理子节点
  if (shapeFlag & 8) {
    // 处理文本节点
    hostSetElementText(el, vnode.children);
  } else if (shapeFlag & 16) {
    // 若节点有子节点,递归挂载子节点
    mountChildren(
      vnode.children,
      el,
      null,
      parentComponent,
      parentSuspense,
      resolveChildrenNamespace(vnode, namespace),
      slotScopeIds,
      optimized
    );
  }
  // 若节点有指令
  if (dirs) {
    // 则调用指令的created钩子
    invokeDirectiveHook(vnode, null, parentComponent, "created");
  }
  // 设置作用域ID,用于Scoped CSS
  setScopeId(el, vnode, vnode.scopeId, slotScopeIds, parentComponent);
  // 处理属性
  if (props) {
    // 遍历props
    for (const key in props) {
      // 将属性设置到元素上,跳过value和保留属性
      if (key !== "value" && !shared.isReservedProp(key)) {
        // 调用hostPatchProp设置属性
        hostPatchProp(el, key, null, props[key], namespace, parentComponent);
      }
    }
    // 单独处理value属性
    if ("value" in props) {
      hostPatchProp(el, "value", null, props.value, namespace);
    }
    // 如果有onVnodeBeforeMount钩子,则调用它
    if (vnodeHook = props.onVnodeBeforeMount) {
      invokeVNodeHook(vnodeHook, parentComponent, vnode);
    }
  }
  // 调用指令的beforeMount钩子
  if (dirs) {
    invokeDirectiveHook(vnode, null, parentComponent, "beforeMount");
  }
  // 处理过渡效果
  const needCallTransitionHooks = needTransition(parentSuspense, transition);
  if (needCallTransitionHooks) {
    transition.beforeEnter(el);
  }
  // 插入元素
  hostInsert(el, container, anchor);
  // 执行挂载后的钩子,推入到队列中
  if ((vnodeHook = props && props.onVnodeMounted) || needCallTransitionHooks || dirs) {
    queuePostRenderEffect(() => {
      vnodeHook && invokeVNodeHook(vnodeHook, parentComponent, vnode);
      needCallTransitionHooks && transition.enter(el);
      dirs && invokeDirectiveHook(vnode, null, parentComponent, "mounted");
    }, parentSuspense);
  }
}
1
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

# 辅助方法

# mountChildren

mountElement方法挂载DOM元素时,若虚拟节点有子节点,则会调用mountChildren挂载子节点。mountChildren就是遍历子节点,然后调用patch方法,在patch方法中根据子节点的type或shapeFlag进行挂载。

const mountChildren = (children, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized, start = 0) => {
    for (let i = start; i < children.length; i++) {
      const child = children[i] = optimized ? cloneIfMounted(children[i]) : normalizeVNode(children[i]);
      patch(
        null,
        child,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        namespace,
        slotScopeIds,
        optimized
      );
    }
  };
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

在mountChildren中调用patch方法传入的child就是新节点,若optimized为true,则说明是优化模式,会调用cloneIfMounted生成节点;否则调用normalizeVNode,该方法会生成虚拟节点。

# cloneIfMounted

cloneIfMounted方法就是判断虚拟节点是否挂载了,若挂载了,就调用cloneVNode方法进行克隆VNode;否则直接返回虚拟节点。

function cloneIfMounted(child) {
  // 虚拟节点未挂载时,el元素为null,patchFlag为-1表示没有缓存
  return child.el === null && child.patchFlag !== -1 || child.memo ? child : cloneVNode(child);
}
1
2
3
4

# cloneVNode

cloneVNode方法用于拷贝复制一份虚拟节点。

function cloneVNode(vnode, extraProps, mergeRef = false, cloneTransition = false) {
  const { props, ref, patchFlag, children, transition } = vnode;
  const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props;
  const cloned = {
    __v_isVNode: true, // 虚拟节点标志位
    __v_skip: true, // 跳过某些优化
    type: vnode.type, // 节点类型
    props: mergedProps, // 合并后的属性
    key: mergedProps && normalizeKey(mergedProps), // 规范化后的可以
    ref: extraProps && extraProps.ref ? (
      mergeRef && ref ? shared.isArray(ref) ? ref.concat(normalizeRef(extraProps)) : [ref, normalizeRef(extraProps)] : normalizeRef(extraProps)
    ) : ref,
    scopeId: vnode.scopeId,
    slotScopeIds: vnode.slotScopeIds,
    children: children,// 直接复用子节点
    target: vnode.target,
    targetStart: vnode.targetStart,
    targetAnchor: vnode.targetAnchor,
    staticCount: vnode.staticCount,
    shapeFlag: vnode.shapeFlag,
    patchFlag: extraProps && vnode.type !== Fragment ? patchFlag === -1 ? 16 : patchFlag | 16 : patchFlag,
    dynamicProps: vnode.dynamicProps,
    dynamicChildren: vnode.dynamicChildren,
    appContext: vnode.appContext,
    dirs: vnode.dirs,
    transition,
    component: vnode.component,
    suspense: vnode.suspense,
    ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
    ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
    placeholder: vnode.placeholder,
    el: vnode.el,
    anchor: vnode.anchor,
    ctx: vnode.ctx,
    ce: vnode.ce
  };
  if (transition && cloneTransition) {
    setTransitionHooks(
      cloned,
      transition.clone(cloned)
    );
  }
  return cloned;
}
1
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

# normalizeVNode

function normalizeVNode(child) {
  if (child == null || typeof child === "boolean") {
    return createVNode(Comment);
  } else if (shared.isArray(child)) {
    return createVNode(
      Fragment,
      null,
      child.slice()
    );
  } else if (isVNode(child)) {
    return cloneIfMounted(child);
  } else {
    return createVNode(Text, null, String(child));
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15

# createVNode

function _createVNode(type, props = null, children = null, patchFlag = 0, dynamicProps = null, isBlockNode = false) {
  if (!type || type === NULL_DYNAMIC_COMPONENT) {
    type = Comment;
  }
  if (isVNode(type)) {
    const cloned = cloneVNode(
      type,
      props,
      true
    );
    if (children) {
      normalizeChildren(cloned, children);
    }
    if (isBlockTreeEnabled > 0 && !isBlockNode && currentBlock) {
      if (cloned.shapeFlag & 6) {
        currentBlock[currentBlock.indexOf(type)] = cloned;
      } else {
        currentBlock.push(cloned);
      }
    }
    cloned.patchFlag = -2;
    return cloned;
  }
  if (isClassComponent(type)) {
    type = type.__vccOpts;
  }
  if (props) {
    props = guardReactiveProps(props);
    let { class: klass, style } = props;
    if (klass && !shared.isString(klass)) {
      props.class = shared.normalizeClass(klass);
    }
    if (shared.isObject(style)) {
      if (reactivity.isProxy(style) && !shared.isArray(style)) {
        style = shared.extend({}, style);
      }
      props.style = shared.normalizeStyle(style);
    }
  }
  const shapeFlag = shared.isString(type) ? 1 : isSuspense(type) ? 128 : isTeleport(type) ? 64 : shared.isObject(type) ? 4 : shared.isFunction(type) ? 2 : 0;
  return createBaseVNode(
    type,
    props,
    children,
    patchFlag,
    dynamicProps,
    shapeFlag,
    isBlockNode,
    true
  );
}
1
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

# setScopeId

const setScopeId = (el, vnode, scopeId, slotScopeIds, parentComponent) => {
  if (scopeId) {
    hostSetScopeId(el, scopeId)
  }
  if (slotScopeIds) {
    for (let i = 0; i < slotScopeIds.length; i++) {
      hostSetScopeId(el, slotScopeIds[i])
    }
  }
  if (parentComponent) {
    let subTree = parentComponent.subTree;
    if (vnode === subTree || isSuspense(subTree.type) && (subTree.ssContent === vnode || subTree.ssFallback === vnode)) {
      const parentVNode = parentComponent.vnode;
      setScopeId(el, parentVNode, parentVNode.scopeId, parentVNode.slotScopeIds, parentComponent.parent)
    }
  }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
编辑 (opens new window)
上次更新: 2025/10/03, 03:52:19
patch中组件的更新解析
patchDOM元素的更新解析

← patch中组件的更新解析 patchDOM元素的更新解析→

最近更新
01
patch中的双端比较快速算法
09-25
02
patchDOM元素的更新解析
09-25
03
patch中组件的更新解析
09-23
更多文章>
Theme by Vdoing | Copyright © 2024-2025 东流 | MIT License
  • 跟随系统
  • 浅色模式
  • 深色模式
  • 阅读模式