最近要做一个组件,但是组件的template部分过于繁琐,因此考虑用render来自动绘制template。关于这一部分内容,官网的介绍不够清晰,其他的教程也大多是千篇一律的复制结果,因此分享下我的关于这一部分的心得。
1. 需求
我要做的是一个业务相关的组件,就是动态地创建表格(包括跟数据相关的一些样式),并往表格里面填入信息,以及对表格中数据的一些操作。由于数据不是固定的,因此只能用函数来解决。之前对Vue不熟悉,我是用元素js加上JQ实现的。现在学了render渲染,因此我用render重新做了一遍。
2. 基础知识
一般来说,我们在创建页面的时候,都倾向于使用模板来实现,这也是官方推荐的方式。但有时我们并不能在一开始就确定我们HTML的内容及样式,这时我们就可以使用渲染函数来解决问题。有时为了简化代码,我们也可以将模板里的内容用render来实现,例如官网里面关于标题的例子。
在使用render函数时,我们需要删除.vue文件中的< template >部分,否则渲染函数的结果无法显示。
2.1 节点、树以及虚拟DOM
关于这一部分,大家可以直接参考官方的介绍。
2.1.1 节点、树
关于DOM节点树这一部分,需要注意的是,在一段HTML代码中,DOM元素可以作为一个节点,一段文字也可以作为节点,一段注释也可以作为节点。 所以在createElement的子节点参数中,我们可以将一段文本作为子节点。
2.2.2 虚拟DOM
Vue 通过建立一个虚拟 DOM 来追踪自己要如何改变真实 DOM。请仔细看这行代码:
return createElement('h1', this.blogTitle)
对于createElement ,它更准确的名字可能是 createNodeDescription,因为它所包含的信息会告诉 Vue 页面上需要渲染什么样的节点,包括及其子节点的描述信息。 我们把这样的节点描述为“虚拟节点 (virtual node)”,也常简写它为“VNode”。“虚拟 DOM”是我们对由 Vue 组件树建立起来的整个 VNode 树的称呼。
2.2 createElement参数
官方给出了createElement函数的API,如下所示
// @returns {VNode}
createElement(
// {String | Object | Function}
// 一个 HTML 标签名、组件选项对象,或者
// resolve 了上述任何一种的一个 async 函数。必填项。
'div',
// {Object}
// 一个与模板中属性对应的数据对象。可选。
{
// (详情见下一节)
},
// {String | Array}
// 子级虚拟节点 (VNodes),由 `createElement()` 构建而成,
// 也可以使用字符串来生成“文本虚拟节点”。可选。
[
'先写一些文字',
createElement('h1', '一则头条'),
createElement(MyComponent, {
props: {
someProp: 'foobar'
}
})
]
)
根据API的内容,我们可以知道,createElement可以有三个函数,一个必选项和两个可选项。
第一个参数可以填HTML标签名,组件选项对象(详见 示例1),或者一个函数(函数还没用过,按下不表)。
第二个参数填一些数据对象,具体下面会说。
第三个参数填子虚拟节点,一般都是调用createElement()创建新的虚拟节点,也可以使用字符串来生成“文本虚拟节点”。
2.2.1 深入数据对象
这一部分的数据主要是createElement()中第二个参数的内容,在这里你可以以对象的方式,填充DOM元素的属性、事件、自定义指令等,详见官方API。对于属性这一块,DOM元素的属性可能有class、style、id等,但在createElement语法中,class和style有特殊的key(class和style,class需要引号)来表示,而id这类的属性用的是另一个key(attrs)。
{
// 与 `v-bind:class` 的 API 相同,
// 接受一个字符串、对象或字符串和对象组成的数组
'class': {
foo: true,
bar: false
},
// 与 `v-bind:style` 的 API 相同,
// 接受一个字符串、对象,或对象组成的数组
style: {
color: 'red',
fontSize: '14px'
},
// 普通的 HTML 特性
attrs: {
id: 'foo'
},
// 组件 prop。当createElement的第一个参数是组件对象时,可以使用这个
props: {
myProp: 'bar'
},
// DOM 属性
domProps: {
innerHTML: 'baz'
},
// 事件监听器在 `on` 属性内,
// 但不再支持如 `v-on:keyup.enter` 这样的修饰器。
// 需要在处理函数中手动检查 keyCode。
// 为DOM元素绑定节点。
on: {
click: this.clickHandler
},
// 仅用于组件,用于监听原生事件,而不是组件内部使用 `vm.$emit` 触发的事件。
// nativeOn与on的区别在于,nativeOn监控的是原生事件(例如按键的click,hover等),而on监控的是自定义事件
nativeOn: {
click: this.nativeClickHandler
},
// 自定义指令。如实现v-show指令
directives: [
{
name: 'show',
value: this.isShow
}
],
// 以下暂时都没用过
// 作用域插槽的格式为
// { name: props => VNode | Array<VNode> }
scopedSlots: {
default: props => createElement('span', props.text)
},
// 如果组件是其它组件的子组件,需为插槽指定名称
slot: 'name-of-slot',
// 其它特殊顶层属性
key: 'myKey',
ref: 'myRef',
// 如果你在渲染函数中给多个元素都应用了相同的 ref 名,
// 那么 `$refs.myRef` 会变成一个数组。
refInFor: true
}
2.2.2 示例1:render的简单示例
<script>
import MyButton from "./MyButton";
export default {
name: "HtmlArg",
render(createElement) {
return createElement(
'div',
{
// class的需要一个引号
'class':{
'div1':true
},
attrs:{
id:'firstDiv',
}
},
// 第三个参数为子节点数组
[
// 第一个子节点
createElement('button',{
'class':{
'div2':true
},
attrs:{
value: 'okay'
},
},
['yellow']
),
// 第二个子节点
createElement('button',{
'class':{
'div3':true
},
attrs:{
value: 'cancel'
}
} ,
['red']),
// 第一个参数除了标签名以外,还可以填组件名
// 第三个子节点
createElement(MyButton)
]
);
}
}
</script>
<style scoped>
.div1{
background: #0059F3;
}
.div2{
background: yellow;
}
.div3{
background: red;
}
</style>
上述代码中定义了一个div标签。在数据对象部分,通过class进行了样式设置,通过attrs设置了该元素的id。最后在第三个参数中定义了所有的子节点,以数组的形式创建了三个子节点,值得注意的是,这里的第三个子子节点调用了我编写了另一个组件,也算是达到了代码复用的目的。不过那个组件的代码很简单,主要是为了演示组件名作为createElement的参数,就不列出来了。结果如下所示,左下角显示的是数据对象的相关内容。
2.2.3 VNode必须唯一
组件树中的VNode必须唯一,官方demo。
2.3 JS语法
2.3.1 v-if
2.3.2 v-for
2.3.3 事件
2.3.4 插槽
2.4 JSX
这里是编写JSX风格的代码,然后利用Babel插件转化成JS风格,我没用,就不多做介绍。
2.5 函数式组件
待会学习下
3. 复杂案例
参考业务上的demo,做了一个简化的业务。
4. 总结
本文是我在认真学习了官网教程,并查阅了相关资料后,总结的结果。针对教程中部分晦涩的地方进行了扩展,教程中相关部分写的不错,因此我省略了并添加链接。欢迎大家留言交流。如果问题,欢迎斧正。