Vue
推荐在绝大多数情况下使用模板来创建你的HTML
。然而在一些场景中,你真的需要JavaScript
的完全编程的能力。这时你可以用渲染函数
先贴官网地址:渲染函数&jsx
官网简单案例h1~h6
-
模板写法
-
render
写法
-
调用
Vue-render
函数
createElement
创建虚拟Dom
-
createElement
到底会返回什么呢?其实不是一个实际的DOM
元素。它更准确的名字可能是createNodeDescription
,因为它所包含的信息会告诉Vue
页面上需要渲染什么样的节点,包括及其子节点的描述信息。我们把这样的节点描述为“虚拟节点 (virtual node
)”,也常简写它为“VNode
”。“虚拟DOM
”是我们对由Vue
组件树建立起来的整个VNode
树的称呼。 - 将
h
作为createElement
的别名是Vue
生态系统中的一个通用惯例,实际上也是JSX
所要求的。 -
vue
核心之虚拟DOM
:这块具体内容另外文章另行学习。
render
函数的三个参数
- 第一个参数:看我下面截图中的Object类型和Function类型,我的环境中报错了;
了解vue里的Runtime Only和Runtime+Compiler
- 第二个参数(可选),配置各种属性/样式/事件,自己看官网吧。有一个说明下:
2.3.0
及以上版本,可以省略props
选项,所有组件上的特性都会被自动隐式解析为prop
- 第三个参数(可选),多个子标签组成的数组,标签的创建方式可以和父标签一致。这里有个注意点:
VNode
必须唯一。啥意思呢?看下面代码
-
如果你真的需要重复很多次的元素/组件,你可以使用工厂函数来实现。例如,下面这渲染函数用完全合法的方式渲染了 20 个相同的段落:
模板文件中的v-if
、v-for
、v-model
在render
中怎么写
-
if/for
其实很简单,就原生javaScript
中的写法喽
-
v-model
就是自定义指令那块内容:
- 再贴一个:
render
中slot
的用法 -
render
中的事件修饰符:大部分用原生的呗,如event.stopPropagation()
;还有个把是在事件前加特有前缀;具体查看官方文档
函数式组件functional
第一个案例中的标题组件是比较简单,没有管理任何状态,也没有监听任何传递给它的状态,也没有生命周期方法。实际上,它只是一个接受一些
prop
的函数。
在这样的场景下,我们可以将组件标记为functional
,这意味它无状态 (没有响应式数据),也没有实例 (没有 this 上下文)。
来看个el-button的简单案例,分别是模板写法和render
写法
注意:
-
template
中的组件el-button
如果是通过components: { elButton }
则报错。为啥呢?回头看下函数组件的说明,没有状态,没有实例属性等,当然components
是失效的喽。 -
v-bind="data.attrs"
v-on="listeners"
这里可以固定这样写,表示父组件中传过来的属性及事件。
调用组件:
来段官方说明,根据例子自行领会下:
render
组件需要的一切都是通过 context
参数传递,它是一个包括如下字段的对象:
-
props
:提供所有prop
的对象 -
children
:VNode
子节点的数组 -
slots
: 一个函数,返回了包含所有插槽的对象 -
scopedSlots
: 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽。 -
data
:传递给组件的整个数据对象,作为createElement
的第二个参数传入组件 -
parent
:对父组件的引用 -
listeners
: 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是data.on
的一个别名。 -
injections
: 如果使用了inject
选项,则该对象包含了应当被注入的属性。
在添加
functional: true
之后,需要更新我们的锚点标题组件的渲染函数,为其增加context
参数,并将this.$slots.default
更新为context.children
,然后将this.level
更新为context.props.level
。
对element-ui
表单的一个二次封装
日期控件:
<template>
<el-date-picker
v-model="modelForm[attr.id]"
v-bind="attr"
v-on="$listeners">
</el-date-picker>
</template>
<script>
export default {
name: 'LwFormDate',
props: {
attr: Object,
modelForm: Object,
}
}
</script>
单选控件
<template>
<el-radio-group
v-model="modelForm[attr.id]"
v-bind="attr"
v-on="$listeners">
<!-- 按钮形式显示 -->
<div v-if="attr.buttonType">
<el-radio-button
v-for="(e, i) in attr.options"
:key="i"
:label="e.value"
:disabled="e.disabled">
{{ e.label }}
</el-radio-button>
</div>
<div v-else>
<el-radio
v-for="(e, i) in attr.options"
:key="i"
:label="e.value"
:disabled="e.disabled"
:border="e.border">
{{ e.label }}
</el-radio>
</div>
</el-radio-group>
</template>
<script>
export default {
name: 'LwFormRadio',
props: {
attr: Object,
modelForm: Object,
}
}
</script>
下拉控件
<template>
<el-select
v-model="modelForm[attr.id]"
v-bind="attr"
v-on="$listeners">
<el-option v-if="attr.includeEmpty" value=""></el-option>
<!-- 选项分组 groupBy:true -->
<div v-if="attr.groupBy">
<el-option-group
v-for="(group,i) in attr.groups"
:key="i" :label="group.label"
:disabled="group.disabled">
<el-option
v-for="(e, j) in group.options"
:key="j"
:label="e.label"
:value="e.value"
:disabled="e.disabled">
<!-- option选项自定义模板 -->
<div v-if="attr.reRender">{{ optRender(e) }}</div>
</el-option>
</el-option-group>
</div>
<!-- 选项不分组 -->
<div v-else>
<el-option
v-for="(e, j) in attr.options"
:key="j"
:label="e.label"
:value="e.value"
:disabled="e.disabled">
<!-- option选项自定义模板 -->
<div v-if="attr.reRender">{{ optRender(e) }}</div>
</el-option>
</div>
</el-select>
</template>
<script>
export default {
name: 'LwFormSelect',
props: {
attr: Object,
modelForm: Object,
optRender: { //下拉选项重新渲染
type: Function,
default(e) {
return `${e.value}-${e.label}`
}
},
}
}
</script>
打分控件
<template>
<el-color-picker
v-model="modelForm[attr.id]"
v-bind="attr"
v-on="$listeners">
</el-color-picker>
</template>
<script>
export default {
name: 'LwFormColor',
props: {
attr: Object,
modelForm: Object,
}
}
</script>
其他就不一一例举了,都差不多,再来个根据xtype
选择哪个控件的render
函数
export default {
name: 'LwFormAitem',
props:{
xtype: {
type: String,
default: 'input'
},
modelForm: Object,
attr: Object,
},
render: function (h) {
return h(
'lw-form-' + this.xtype, // 标签名称
{
props:{
modelForm: this.modelForm,
attr:this.attr
}
}
)
}
}
再将label
名称放到一起
<template>
<el-form-item
:label="attr.label+splitSymbol"
:prop="attr.id"
:required="attr.required"
:rules="attr.rules">
<lw-form-aitem
:attr="attr"
:modelForm="modelForm"
:xtype="xtype">
</lw-form-aitem>
</el-form-item>
</template>
<script>
export default {
name: 'LwFormBitem',
props: {
splitSymbol:{
type: String,
default: ':'
},
xtype: String,
attr: Object,
modelForm: Object,
},
}
</script>
最后再来完整的form
<template>
<el-form
:model="modelForm"
v-bind="$attrs"
ref="lwform">
<lw-form-bitem
v-for="(attr, i) in attrs"
:key="i"
:attr="attr"
:modelForm="modelForm"
:xtype="attr.xtype"
:splitSymbol="splitSymbol">
</lw-form-bitem>
<slot name="formBtn"></slot>
</el-form>
</template>
<script>
export default {
name: 'LwForm',
props: {
splitSymbol:String, //标签名称后的符号,默认为冒号
modelForm: Object, //表单绑定值
attrs: Array, //各字段表单控件对应的属性
},
methods: {
validate(callback) {
this.$refs.lwform.validate(callback);
},
resetFields() {
this.$refs.lwform.resetFields();
}
}
}
</script>
使用上述表单的案例:
<template>
<div style="width:50%">
<lw-form
:modelForm="modelForm"
:attrs="attrs"
labelWidth="100px"
ref="formlw">
<el-form-item slot="formBtn">
<el-button type="primary" @click="submitForm">提交</el-button>
<el-button @click="resetForm">重置</el-button>
<el-button @click="mockTest">mock测试</el-button>
</el-form-item>
</lw-form>
</div>
</template>
<script>
import axios from 'axios'
export default {
data() {
return {
modelForm:{
name: null,
age: null,
sex: null,
city: null,
enjoy:[],
rate: null,
},
attrs:[
{id:"name", label:"姓名", required:true},
{id:"birth", label:"生日", xtype:'date'},
{id:"age", label:"年龄", xtype:"number"},
{id:"sex", label:"性别", xtype:"radio",
options:[{label:"男", value:"1"},{label:"女", value:"2"}]},
{id:"city", label:"省份", xtype:"radio",
options:[{label:"浙江", value:"zj"},{label:"江苏", value:"js"},{label:"四川", value:"sc"}]},
{id:"enjoy", label:"爱好", xtype:"checkbox",
options:[{label:"读书", value:"0"},{label:"旅游", value:"1"},{label:"唱歌", value:"2"}]},
{id:"rate", label:"自我评测", xtype:"rate", allowHalf:true}
]
};
},
methods: {
submitForm() {
this.$refs.formlw.validate(valid => {
if (valid) {
console.log(this.modelForm)
alert("请到控制台查看表单信息!")
}
})
},
resetForm() {
this.$refs.formlw.resetFields();
},
mockTest() {
axios.get("/student/list").then(res=>{
console.log(res)
})
}
}
}
</script>
模板编译
你可能会有兴趣知道,
Vue
的模板实际上被编译成了渲染函数。这是一个实现细节,通常不需要关心。但如果你想看看模板的功能具体是怎样被编译的,可能会发现会非常有意思
从这个文件src/platforms/web/entry-runtime-with-compiler.js
入口开始学习吧。
先看看VNode和diff算法,再来看这个,后面学习了再进行总结喽...