在之前的章节中我们实现了 ELEMENT
节点的挂载与更新,以及 class
、style
、事件
属性等等的挂载与更新。
本章我们在来实现一下 Text
、Comment
、Fragment
节点的渲染吧
首先创建测试实例:
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>
我们知道,对于节点的打补丁操作是从 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
直接指定即可。明确好了 Text
的源码逻辑之后,那么接下来我们就实现一下对应的代码:
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)
}
}
}
RendererOptions
增加 createText
与 setText
方法:ts /**
* 渲染器配置对象
*/
export interface RendererOptions {
...
/**
* 创建 Text 节点
*/
createText(text: string)
/**
* 设置 text
*/
setText(node, text): void
}
options
增加解析:ts/**
* 解构 options,获取所有的兼容性方法
*/
const {
...
createText: hostCreateText,
setText: hostSetText
} = options
patch
方法中,处理 Text
节点:tscase Text:
// Text
processText(oldVNode, newVNode, container, anchor)
break
packages/runtime-dom/src/nodeOps.ts
增加 createText
和 setText
方法: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>
测试挂载和更新成功
完成了 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>
processCommentNode
方法:从上图可以看出挂载操作会进入到 hostInsert
,而 hostInsert
实际上就是执行的 createComment
。另外也可以看到在 else
中的注释以及代码,说明 vue
确实不支持动态的 comment
,而是直接替换了 el
至此,挂载成功
总结:
由以上代码可知:
Comment
而言,只存在 挂载 操作doc.createComment
创建 Comment
节点hostInsert
完成挂载packages/runtime-core/src/renderer.ts
中:tsexport 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
方法: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>
测试成功
本文作者:叶继伟
本文链接:
版权声明:本博客所有文章除特别声明外,均采用 BY-NC-SA 许可协议。转载请注明出处!