2023-01-07
Vue
00

目录

前言
1. TEXT 节点的挂载与更新
1.1 源码阅读
1.2 代码实现
2. COMMENT 节点的挂载和更新
2.1 源码阅读
2.2 代码实现

前言

在之前的章节中我们实现了 ELEMENT 节点的挂载与更新,以及 classstyle事件 属性等等的挂载与更新。

本章我们在来实现一下 TextCommentFragment 节点的渲染吧

1. TEXT 节点的挂载与更新

首先创建测试实例:

html
<script> const { h, render, Text } = Vue const vnode = h(Text, 'hello world') // 挂载 render(vnode, document.querySelector('#app')) // 延迟两秒,生成新的 vnode,进行更新操作 setTimeout(() => { const vnode2 = h(Text, '你好,世界') render(vnode2, document.querySelector('#app')) }, 2000) </script>

1.1 源码阅读

我们知道,对于节点的打补丁操作是从 packages/runtime-core/src/renderer.ts 中的 render 函数开始的,所以我们可以直接在这里进行 debugger

image.png

  1. 第一次进入 render ,执行挂载操作,可以看到 render 方法会执行 patch 方法:

image.png

  1. patch 方法中 会进行 switch(type) 判断,因为此时我们是 TEXT 节点 所以会进入 processText 方法。同时我们也看到了下面 Comment 会进入 processCommentNode 方法 Fragment 会进入 processFragment 方法。我们先进入 processText 方法:

image.png

  1. processText 方法中,因为 n1 == null,所以会执行 hostInsert 方法,但是在 hostInsert 中的第一个参数会先调用 hostCreateText,所以执行顺序是先执行 hostCreateText 在执行 hostInsert:

image.png

  1. 可以看到这两个方法都是在源码 /packages/runtime-dom/src/nodeOps.ts 文件中,之前我们也说过,vue 为了浏览器兼容性,特定将操作 dom 的操作都放在了 runtime-dom 的文件夹下了,好我们再来看着两个方法,第一个 hostCreateText 方法很简单就是执行了 createTextNode 生成 Text 节点,第二个 hostInsert 方法则执行了 insertBefore,该方法我们之前实现过,就不在多说了。

  2. 至此 挂载 操作完成

  3. 延迟两秒,第二次进入 render 方法,执行 更新操作

  4. 第二次依然会 进入 render patch processText 方法,我们直接从 processText 方法调试:

image.png

  1. processText 中, n1.children = 'hello world',n2.children = '你好世界'if 条件成立,所以会进入到 hostSetText

image.png

  1. hostSetText 执行了 node.nodeValue = text 修改了 value

  2. 至此,更新操作完成。

总结:

由以上代码可知:

  1. 对于 Text 节点的 挂载和更新,整体是非常简单的:
  2. 挂载:通过 doc.createTextNode(text) 生成节点,在通过 insertBefore 插入
  3. 更新:通过 node.nodeValue 直接指定即可。

1.2 代码实现

明确好了 Text 的源码逻辑之后,那么接下来我们就实现一下对应的代码:

  1. packages/runtime-core/src/renderer.ts 中,增加 processText 方法:
ts
/** * Text 的打补丁操作 */ const processText = (oldVNode, newVNode, container, anchor) => { // 不存在旧的节点,则为 挂载 操作 if (oldVNode == null) { // 生成节点 newVNode.el = hostCreateText(newVNode.children as string) // 挂载 hostInsert(newVNode.el, container, anchor) } // 存在旧的节点,则为 更新 操作 else { const el = (newVNode.el = oldVNode.el!) if (newVNode.children !== oldVNode.children) { hostSetText(el, newVNode.children as string) } } }
  1. RendererOptions 增加 createTextsetText 方法:
ts
/** * 渲染器配置对象 */ export interface RendererOptions { ... /** * 创建 Text 节点 */ createText(text: string) /** * 设置 text */ setText(node, text): void }
  1. options 增加解析:
ts
/** * 解构 options,获取所有的兼容性方法 */ const { ... createText: hostCreateText, setText: hostSetText } = options
  1. patch 方法中,处理 Text 节点:
ts
case Text: // Text processText(oldVNode, newVNode, container, anchor) break
  1. packages/runtime-dom/src/nodeOps.ts 增加 createTextsetText 方法:
ts
/** * 创建 Text 节点 */ createText: text => doc.createTextNode(text), /** * 设置 text */ setText: (node, text) => { node.nodeValue = text }

代码完成。

创建测试实例 packages/vue/examples/runtime/render-text.html

html
<script> const { h, render, Text } = Vue const vnode = h(Text, 'hello world') // 挂载 render(vnode, document.querySelector('#app')) // 延迟两秒,生成新的 vnode,进行更新操作 setTimeout(() => { const vnode2 = h(Text, '你好,世界') render(vnode2, document.querySelector('#app')) }, 2000) </script>

测试挂载和更新成功

2. COMMENT 节点的挂载和更新

完成了 Text 节点的挂载、更新之后,那么下面我们来看 Comment 节点的挂载操作。

这里要注意的是:vue 不支持动态的 comment,即没有更新操作

其实对于 Comment 而言,它的整体流程和 Text 非常类似。我们在 1.1 源码阅读的第 2 步中就已经说过了,它会进入 processCommentNode 方法,下面我们创建一个测试实例直接进入这个方法

html
<script> const { h, render, Comment } = Vue const vnode = h(Comment, 'hello world') // 挂载 render(vnode, document.querySelector('#app')) </script>

2.1 源码阅读

  1. 进入 processCommentNode 方法:

image.png

  1. 从上图可以看出挂载操作会进入到 hostInsert,而 hostInsert 实际上就是执行的 createComment。另外也可以看到在 else 中的注释以及代码,说明 vue 确实不支持动态的 comment,而是直接替换了 el

  2. 至此,挂载成功

总结:

由以上代码可知:

  1. 对于 Comment 而言,只存在 挂载 操作
  2. 整体的处理非常简单:
    1. 通过 doc.createComment 创建 Comment 节点
    2. 通过 hostInsert 完成挂载

2.2 代码实现

  1. packages/runtime-core/src/renderer.ts 中:
ts
export interface RendererOptions { ... /** * 设置 text */ createComment(text: string) } const { ... createComment: hostCreateComment } = options /** * Comment 的打补丁操作 */ const processCommentNode = (oldVNode, newVNode, container, anchor) => { if (oldVNode == null) { // 生成节点 newVNode.el = hostCreateComment((newVNode.children as string) || '') // 挂载 hostInsert(newVNode.el, container, anchor) } else { // 无更新 newVNode.el = oldVNode.el } } // patch 方法中 switch 逻辑 case Comment: // Comment processCommentNode(oldVNode, newVNode, container, anchor) break
  1. packages/runtime-dom/src/nodeOps.ts 中,增加 createComment 方法:
ts
/** * 创建 Comment 节点 */ createComment: (text) => doc.createComment(text)

代码完成。

创建测试实例 packages/vue/examples/runtime/render-comment.html:

html
<script> const { h, render, Text } = Vue const vnode = h(Text, 'hello world') // 挂载 render(vnode, document.querySelector('#app')) // 延迟两秒,生成新的 vnode,进行更新操作 setTimeout(() => { const vnode2 = h(Text, '你好,世界') render(vnode2, document.querySelector('#app')) }, 2000) </script>

测试成功

如果对你有用的话,可以打赏哦
打赏
ali pay
wechat pay

本文作者:叶继伟

本文链接:

版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!