在前面的内容我们学习到了Vue的响应式,但是只有响应式是不够的,我们要将内容渲染出来。Vue的templates就是通过Render(渲染)函数渲染出来的。
第一阶段(生成虚拟DOM)
在Vue中,如果你把模版直接传入Vue实例,那Vue会执行完整的编译,把传入的template编译为浏览器可运行的DOM,就像你直接在DOM中编写模板一样。
如果你使用vue-cli构建项目,会用到webpack和vue-loader。它会在构建时预编译模板为可以直接解析的DOM代码(h()函数),即纯JavaScript代码。还有另一种编译模式,就是将编译器也打包进去,压缩之后会比第一种体积大一些。
上面两种情况其实都是使用Render函数生成虚拟DOM。
第二阶段(生成真实DOM)
Vue会基于第一阶段的虚拟DOM转换成真实DOM。
虚拟DOM更新
第一、二阶段相当于完成了初始化,如果之后DOM需要更新呢?回顾之前讲的autorun函数(用于订阅发布),我们可以将虚拟DOM的代码放在里面,当数据改变时,Render函数会生成新的虚拟DOM,即触发我们的发布,新的虚拟DOM和旧的虚拟DOM进行比较,得出最少需要更新的节点并生成真实DOM完成一次更新。
简单来说,就是将真实的DOM转化成JS代码,既然都是JS代码,虚拟DOM产生变化的时候也只需要修改JS的一些内容,最终再转换成真实DOM。
你可能会好奇,我本来直接操作DOM就可以,为什么要在中间加一系列操作呢?
这是由于操作真实DOM中会存在一些问题,当DOM节点非常多的时候:会有资源消耗问题和执行效率问题。
使用JavaScript操作真实DOM会非常消耗资源,因为要修改真实DOM操作的内容很多,但是如果使用虚拟DOM,你无论是如何增删修改节点,都只是在操作JS,这样会很节省资源。同样,这样只操作JS,用JS计算差异,也会比真实DOM比较差异快很多。(当然:如果你能将DOM操作到炉火纯青,保证每次对DOM的操作都是较为节约快捷的方式,那还是要比虚拟DOM快的,毕竟我们多了一步将它转换成了JS,又变回真实DOM的过程)
Hello World
调用Render函数转化为虚拟DOM
import { openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"
export function render(_ctx, _cache, $props, $setup, $data, $options) {
return (_openBlock(), _createElementBlock("div", null, "Hello World"))
}
现在我们来整合一下之前的所有知识
每个组件都有一个渲染函数,它包装在我们第一节实现的autorun函数中,当执行渲染的时候,getters收集它的依赖。同时,每个组件都有一个watcher观察者去监听,一旦数据更新或依赖的渲染属性发生变化,我们就执行收集的依赖(即渲染函数),这样每个组件即可实现自己的自动循环渲染。

export default{
render(h){
return h('div',{},[...])
}
}
上面是渲染函数的使用,你需要一个参数h,h是一种简写表示超脚本(HyperScript),这是一种虚拟DOM渲染函数的编写风格。就像超文本叫HTML一样,他没有什么特殊的含义,只是方便书写的表现形式而已。
接下来我们分析一下h()
h()接收三个函数,第一个是元素类型;第二个是参数对象();第三个是子节点
h('div','some text')
h('div',{class:'foo'},'some text')
h('div',{...},[
'some text',
h('span','bar')
])
最后一个的渲染效果就像
some text bar
h()同样也接受一个组件定义,这将会创建一个组件实例
import MyComponent from '...'
h(MyComponent,{
props:{...}
})
前面说了这么多,让我们来做一个demo真正体验一下
传入tags数组
渲染出标签并显示元素索引值
0
1
2
如果经常使用Vue,你的第一反应可能是使用一个构造器,动态渲染
但是这个组件设计的初衷是用来在组件间动态切换,而不是渲染真实的标签;如果Vue没有将这个api提供给我们,我们的开发就会受限。这也是模板的一个缺点,不如jsx灵活。开发者总不能等待vue官方提供解决办法,所以在这里我们使用渲染函数。
我们通过example创建的模板就是这样
0
1
2
上面我们使用的是Vue的状态组件,而函数组件不包含state和props,你可以理解它就是一个函数。
const foo = {
functional: true,
render: h => h('div','foo')
}
函数组件特点:
render(h, context)我们改写一下上面的demo
Vue.component('example', {
functional: true,
props: {
tags: {
type: Array,
validator (arr) { return !!arr.length }
}
},
render: (h, context) => {
const tags = context.props.tags
return h('div', context.data, tags.map((tag, index) => h(tag, index)))
}
})
new Vue({ el: '#app' })
渲染函数除了可以渲染普通标签外,还可以渲染组件,下面代码有Foo和Bar组件,点击toggle按钮的时候,切换两组件的显示状态。