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方法详解
      • 概览
      • 源码解析
        • 参数说明
        • 源码分析
        • 关键设计思想
      • 辅助方法
        • processText处理文本
        • processCommentNode 处理注释节点
        • mountStaticNode 挂载静态节点
        • processFragment 处理片段
        • processElement 处理元素
        • processComponent处理组件
    • patch中组件的挂载解析
    • patch中组件的更新解析
    • patch中DOM元素的挂载解析
    • patchDOM元素的更新解析
    • patch中的双端比较快速算法
  • runtime-dom运行时DOM模块

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

patch方法详解

# 概览

patch方法是vue3中虚拟DOM更新的核心方法,负责比较新旧虚拟节点(VNode)并更新DOM

# 源码解析

# 参数说明

patch的参数说明如下:

  • n1:旧的VNode,若为null,则表示需要挂载
  • n2:新的VNode
  • container:容器DOM元素
  • anchor:参考节点(插入新节点时使用)
  • parentComponent:父组件实例
  • parentSuspense:父级Suspense实例
  • namespace:命名空间(用于SVG等)
  • slotScopeIds:插槽作用域ID(用于优化)
  • optimized:是否开启优化模式(默认根据新节点是否有动态子节点决定)

# 源码分析

源码分析如下:

const patch = (n1, n2, container, anchor = null, parentComponent = null, parentSuspense = null, namespace = void 0, slotScopeIds = null, optimized = !!n2.dynamicChildren) => {
    if (n1 === n2) {
    // 若新旧节点是同一个对象,则直接退出,无需更新  
        return;
    }
    // 比较节点类型
    if (n1 && !isSameVNodeType(n1, n2)) {
        // 若新旧节点类型不同,则
        // 获取旧节点的下一个DOM节点作为锚点
        anchor = getNextHostNode(n1);
        // 卸载旧节点 
        unmount(n1, parentComponent, parentSuspense, true);
        // 将n1置空,后续挂载n2,而不是更新
        n1 = null;
    }
    // 判断n2的patchFlag
    if (n2.patchFlag === -2) {
        // 关闭优化模式,强制全量Diff
        optimized = false;
        n2.dynamicChild = null;
    }
    // 获取节点类型
    const { type, ref, shapeFlag } = n2;
    switch (type) {
        // 文本节点
        case Text:
            processText(n1, n2, container, anchor);
            break;
        // 注释节点   
        case Comment:
            processCommentNode(n1, n2, container, anchor);
        // 静态节点    
        case Static:
            if (n1 == null) {
                mountStaticNode(n2, container, anchor, namespace);
            };
            break;
        // Fragment 片段    
        case Fragment:
            processFragment(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized);
            break;
        // 其他类型通过shapeFlag判断    
        default:
            if (shapeFlag & 1) {
            // Element 普通DOM元素  
                processElement(n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopedIds, optimized)
            } else if (shapeFlag & 6) {
            // Component 组件 有状态  
                processComponent(n1, n2, container, anchor, parentComponent,
                    parentSuspense,
                    namespace,
                    slotScopeIds,
                    optimized)
            } else if (shapeFlag & 64) {
            // Teleport 组件  
                type.process(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    namespace,
                    slotScopeIds,
                    optimized,
                    internals
                )
            } else if (shapeFlag & 128) {
            // Suspense 组件  
                type.process(
                    n1,
                    n2,
                    container,
                    anchor,
                    parentComponent,
                    parentSuspense,
                    namespace,
                    slotScopeIds,
                    optimized,
                    internals
                );;
            }
    }
    // 处理 Ref引用
    if (ref != null && parentComponent) {
        setRef(ref, n1 && n1.ref, parentSuspense, n2 || n1, !n2);
    } else if (ref == null && n1 && n1.ref != null) {
        setRef(n1.ref, null, parentSuspense, n1, 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
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

# 关键设计思想

1.双端Diff优化

  • 通过optimized标记跳过静态子树比对
  • 动态子节点dynamicChildren直接进行定向更新

2.PatchFlags优化

  • 标志位控制Diff策略,减少不必要的操作

3.组件与DOM分离

  • 组件通过processComponent处理生命周期/状态
  • DOM元素通过processElement处理属性/子节点

4.特殊组件内置逻辑

  • Teleport/Suspense直接调用其内部方法,实现跨容器/异步渲染

# 辅助方法

# processText处理文本

processText用于创建/更新文本节点。

processText的源码如下:

const processText = (n1, n2, container, anchor) => {
    if (n1 == null) {
      // 旧节点 n1为null,则直接挂载n2
      // hostCreateText创建文本节点,再调用hostInsert将文本节点插入到container容器中
      hostInsert(n2.el = hostCreateText(n2.children), container, anchor)
    } else {
      const el = n2.el = n1.el;
      // 比较n1和n2的子元素children,若二者不同,则调用hostSetText设置文本
      if (n2.children !== n1.children) {
        hostSetText(el, n2.children);
      }
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13

hostInsert、hostCreateText与hostSetText都是在runtime-dom中实现的操作DOM元素的方法。

# processCommentNode 处理注释节点

processCommentNode用于创建或者是更新注释节点。

processCommentNode的源码实现如下:

const processCommentNode = (n1,n2,container,anchor)=>{
    if(n1 == null){
      // 若n1为null,则挂载n2的el
     hostInsert(n2.el = hostCreateComment(n2.children || ""),container,anchor)    
    }else{
     n2.el = n1.el;
    }
}
1
2
3
4
5
6
7
8

同上,hostCreateComment同样也是在runtime-dom中实现的操作注释节点。

# mountStaticNode 挂载静态节点

mountStaticNode用于挂载静态节点,当n1为null且n2的节点类型为静态节点时,才会执行该方法。

const mountStaticNode = (n2, container, anchor, namespace) => {
    [n2.el, n2.anchor] = hostInsertStaticContent(n2.children, container, anchor, namespace, n2.el, n2.anchor)
}
1
2
3

# processFragment 处理片段

processFragment用于处理Fragment片段节点的核心函数。

const processFragment = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
    // 创建/复用锚点节点,若n1存在,则是更新,复用旧节点的锚点;否则是挂载,创建两个空文本节点标记起止位置
    const fragmentStartAnchor = n2.el = n1 ? n1.el : hostCreateText("");
    const fragmentEndAnchor = n2.anchor = n1 ? n1.anchor : hostCreateText("");
    let { patchFlag, dynamicChildren, slotScopeIds, fragmentSlotScopeIds } = n2;
    
    // 处理插槽作用域ID
    if (fragmentSlotScopeIds) {
      // 若Fragment自身有插槽作用域ID,则将其合并到slotScopeIds中,用于子节点更新优化 
      slotScopeIds = slotScopeIds ? slotScopeIds.concat(fragmentSlotScopeIds) : fragmentSlotScopeIds;
    }
    // 若n1为null,则说明是挂载
    if (n1 == null) {
        // 通过hostInsert插入起止(空文本)节点
        hostInsert(fragmentStartAnchor, container, anchor);
        hostInsert(fragmentEndAnchor, container, anchor);
        // 调用mountChildren挂载子节点
        mountChildren(n2.children || [], container, fragmentEndAnchor, parentComponent, parentSuspense, namespace, slotScopedIds, optimized)
    } else {
     //更新操作  
        if (patchFlag > 0 && patchFlag & 64 && dynamicChildren && n1.dynamicChildren) {
           // patchFlag > 0说明存在编译时优化标记
           // patchFlag & 64 说明子节点顺序稳定,标记为STABLE_FRAGMENT
           // 新旧节点均有动态子节点数组 
           // 动态子节点比对
           patchBlockChildren(n1.dynamicChildren, dynamicChildren, container, parentComponent, parentSuspense, namespace, slotScopeIds)
            // 当Fragment是根节点或带key时处理静态子节点,更新静态节点的引用
            if (n2.key != null || parentComponent && n2 === parentComponent.subTree) {
                traverseStaticChildren(n1, n2, true)
            }
        } else {
          // 全量子节点比对
            patchChildren(n1, n2, container, fragmentEndAnchor, parentComponent, parentSuspense, namespace, slotScopedIds, optimized)
        }
    }
}
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

patchBlockChildren是仅比对动态子节点,跳过静态节点比对,可以大幅提升性能。

# processElement 处理元素

processElement用于处理普通Element元素。

const processElement = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
    // 处理命名空间
    if (n2.type === "svg") {
      namespace = "svg";
    } else if (n2.type === "math") {
      namespace = "mathml";
    }
    // 若旧节点n1为null,则调用mountElement方法挂载n2新节点
    if (n1 == null) {
      mountElement(
        n2,
        container,
        anchor,
        parentComponent,
        parentSuspense,
        namespace,
        slotScopeIds,
        optimized
      );
    } else {
      // 否则调用patchElement更新节点
      patchElement(
        n1,
        n2,
        parentComponent,
        parentSuspense,
        namespace,
        slotScopeIds,
        optimized
      );
    }
  };
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

# processComponent处理组件

processComponent用于更新或挂载组件。

processComponent的源码实现如下:

 const processComponent = (n1, n2, container, anchor, parentComponent, parentSuspense, namespace, slotScopeIds, optimized) => {
    n2.slotScopeIds = slotScopeIds;
    if (n1 == null) {
      // 若旧节点n1为null,则挂载n2新节点,或者激活新节点n2
      if (n2.shapeFlag & 512) {
        // 判断shapeFlag,若是keepAlive包裹的组件,则激活该组件
        parentComponent.ctx.activate(
          n2,
          container,
          anchor,
          namespace,
          optimized
        );
      } else {
        // 调用mountComponent挂载新节点n2
        mountComponent(
          n2,
          container,
          anchor,
          parentComponent,
          parentSuspense,
          namespace,
          optimized
        );
      }
    } else {
      // 调用updateComponent更新组件
      updateComponent(n1, n2, optimized);
    }
  };
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
编辑 (opens new window)
上次更新: 2025/09/23, 10:36:42
渲染器renderer源码解析
patch中组件的挂载解析

← 渲染器renderer源码解析 patch中组件的挂载解析→

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