前言
原文来自 我的个人博客
在之前的章节中我们实现了 ELEMENT
节点的挂载与更新,以及 class
、style
、事件
属性等等的挂载与更新。
本章我们就在来实现一下 Text
、Comment
节点的渲染
1. TEXT 节点的挂载与更新
首先创建测试实例:
<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
:
- 第一次进入
render
,执行挂载操作,可以看到render
方法会执行patch
方法:
- 在
patch
方法中 会进行switch(type)
判断,因为此时我们是TEXT
节点 所以会进入processText
方法。同时我们也看到了下面Comment
会进入processCommentNode
方法Fragment
会进入processFragment
方法。我们先进入processText
方法:
- 在
processText
方法中,因为n1 == null
,所以会执行hostInsert
方法,但是在hostInsert
中的第一个参数会先调用hostCreateText
,所以执行顺序是先执行hostCreateText
在执行hostInsert
:
- 可以看到这两个方法都是在源码
/packages/runtime-dom/src/nodeOps.ts
文件中,之前我们也说过,vue
为了浏览器兼容性,特定将操作dom
的操作都放在了runtime-dom
的文件夹下了,好我们再来看着两个方法,第一个hostCreateText
方法很简单就是执行了createTextNode
生成Text
节点,第二个hostInsert
方法则执行了insertBefore
,该方法我们之前实现过,就不在多说了。 - 至此 挂载 操作完成
- 延迟两秒,第二次进入
render
方法,执行 更新操作 - 第二次依然会 进入
render
patch
processText
方法,我们直接从processText
方法调试:
- 在
processText
中,n1.children = 'hello world',n2.children = '你好世界'
,if
条件成立,所以会进入到hostSetText
:
- 而
hostSetText
执行了node.nodeValue = text
修改了value
- 至此,更新操作完成。
总结:
由以上代码可知:
- 对于
Text
节点的 挂载和更新,整体是非常简单的: - 挂载:通过
doc.createTextNode(text)
生成节点,在通过insertBefore
插入 - 更新:通过
node.nodeValue
直接指定即可。
1.2 代码实现
明确好了 Text
的源码逻辑之后,那么接下来我们就实现一下对应的代码:
- 在
packages/runtime-core/src/renderer.ts
中,增加processText
方法:
/**
* 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)
}
}
}
- 为
RendererOptions
增加createText
与setText
方法:
/**
* 渲染器配置对象
*/
export interface RendererOptions {
...
/**
* 创建 Text 节点
*/
createText(text: string)
/**
* 设置 text
*/
setText(node, text): void
}
- 为
options
增加解析:
/**
* 解构 options,获取所有的兼容性方法
*/
const {
...
createText: hostCreateText,
setText: hostSetText
} = options
- 在
patch
方法中,处理Text
节点:
case Text:
// Text
processText(oldVNode, newVNode, container, anchor)
break
- 在
packages/runtime-dom/src/nodeOps.ts
增加createText
和setText
方法:
/**
* 创建 Text 节点
*/
createText: text => doc.createTextNode(text),
/**
* 设置 text
*/
setText: (node, text) => {
node.nodeValue = text
}
代码完成。
创建测试实例 packages/vue/examples/runtime/render-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>
测试挂载和更新成功
2. COMMENT 节点的挂载和更新
完成了 Text
节点的挂载、更新之后,那么下面我们来看 Comment
节点的挂载操作。
这里要注意的是:vue
不支持动态的 comment
,即没有更新操作
其实对于 Comment
而言,它的整体流程和 Text
非常类似。我们在 1.1
源码阅读的第 2
步中就已经说过了,它会进入 processCommentNode
方法,下面我们创建一个测试实例直接进入这个方法
<script>
const { h, render, Comment } = Vue
const vnode = h(Comment, 'hello world')
// 挂载
render(vnode, document.querySelector('#app'))
</script>
2.1 源码阅读
- 进入
processCommentNode
方法:
- 从上图可以看出挂载操作会进入到
hostInsert
,而hostInsert
实际上就是执行的createComment
。另外也可以看到在else
中的注释以及代码,说明vue
确实不支持动态的comment
,而是直接替换了el
- 至此,挂载成功
总结:
由以上代码可知:
- 对于
Comment
而言,只存在 挂载 操作 整体的处理非常简单:
- 通过
doc.createComment
创建Comment
节点 - 通过
hostInsert
完成挂载
- 通过
2.2 代码实现
- 在
packages/runtime-core/src/renderer.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
- 在
packages/runtime-dom/src/nodeOps.ts
中,增加createComment
方法:
/**
* 创建 Comment 节点
*/
createComment: (text) => doc.createComment(text)
代码完成。
创建测试实例 packages/vue/examples/runtime/render-comment.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>
测试成功