前言
用Vue已经有2年多了,用本文详解讲解Vue组件开发过程中的方方面面,供大家学习,如果觉得还可以,点个赞收藏一下,持续更新中。
一、组件的注册
1、注册组件时,怎么给组件命名,要什么要求?
给组件命名有两种方式,一种是使用链式命名my-component
,一种是使用大驼峰命名MyComponent
,
在字符串模板中<my-component></my-component>
和 <MyComponent></MyComponent>
都可以使用,
在非字符串模板中最好使用<MyComponent></MyComponent>
,因为要遵循 W3C 规范中的自定义组件名
(字母全小写且必须包含一个连字符),避免和当前以及未来的 HTML 元素相冲突。
2、怎么注册全局组件
Vue.component('my-component', {
props:{
title:{
type: String,
default:'全局组件',
}
},
data(){
return {}
},
created(){},
mounted(){},
methods:{
handleClick(){
alert(this.title)
}
},
template: '<h3 @click="handleClick">{{ title }}</h3>',
})
3、如果template内容很多,那该怎么办?
在项目中static文件中建立my_component.html文件,文件内容如下
<h3 @click="handleClick">{{ title }}</h3>
Vue.component('my-component', (resolve, reject) =>{
// 可以请求一个html文件,既然存放模板还是html文件存放比较好
axios.get("../static/my_component.html").then(res =>{
resolve({
template: res,
props: {
title: {
type: String,
default: '全局组件',
}
},
methods: {
handleClick() {
alert(this.title)
}
},
})
});
});
4、局部组件怎么注册?
-
同步注册
import myComponent from './my_component'; export default { components: { myComponent, }, }
-
异步注册(懒加载)
export default{ components:{ myComponent: resolve => require(['./my_component'],resolve) } }
二、组件的props选项
1、props中每个prop命名有什么要注意的?
在非字符串模板中,如果prop是用小驼峰方式命名,在模板中要使用其等价的链式方式命名。
在字符串模板中就不要这么处理了。
index.vue文件
<template>
<myComponent :my-title="my_title"></myComponent>
</template>
<script>
export default{
data(){
return{
my_title:"我的组件"
}
},
components:{
myComponent:resolve => require(['./my_component'],resolve)
}
}
</script>
my_component.vue文件
<template>
<div>
<h1>{{myTitle}}</h1>
</div>
</template>
<script>
export default{
props:{
myTitle:{
type:String,
default:'',
}
},
data(){
return{}
},
}
</script>
2、为什么在非字符串模板中要这么使用?
因为HTML(非字符串模板)中的特性名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。
3、怎么给prop传入一个静态值?
<myComponent my-title="我的组件"></myComponent>
4、怎么给prop传入一个数字和布尔值?
<myComponent :num="100"></myComponent>
<myComponent :isload="true"></myComponent>
5、在子组件中可以改变prop值吗?为什么
不能,防止从子组件意外改变父级组件的状态。
6、在子组件中怎么防止改变prop值?
-
在data中定义一个值,并将prop值赋值给它,如果prop值是对象和数组时,赋值时要深拷贝一下。
<script> export default{ props:{ myTitle:{ type:String, default:'', }, myInfo:{ type:Object, default(){ return:{} } } }, //this.deepClone为自定义的深拷贝方法 data(){ return{ title:this.myTitle, info:this.deepClone(this.myInfo), } }, } </script>
2.如果prop以一种原始的值传入且需要进行转换。要使用这个 prop 的值来定义一个计算属性。
<script> export default{ props:{ myAchievement:{ type:Number, default:0, }, }, data(){ return{} }, computed: { result: function () { return this.myAchievement*0.7+12; } } } </script>
7、prop允许有哪些类型?
String、Number、Boolean、Array、Object、Date、Function、Symbol。
此外还可以是一个自定义的构造函数Personnel
,并且通过 instanceof 来验证propwokrer
的值是否是通过这个自定义的构造函数创建的。
function Personnel(name,age){
this.name = name;
this.age = age;
}
export default {
props:{
wokrer:Personnel
}
}
8、怎么对prop进行类型验证拦截
export default {
props:{
propA:String,
propB:{
type:Number,
},
propC:Boolean,
}
}
9、怎么对prop进行多个类型验证拦截
export default {
props:{
propA:[String,Number],
propB:{
type:[String,Number]
}
}
}
10、怎么对prop进行必填验证拦截
export default{
props:{
propA:{
type: String,
required: true,
}
}
}
11、怎么给对象和数组类型的prop设置默认值
export default{
props:{
propA:{
type:Object,
default(){
return {
a: 1,
}
}
},
propB:{
type:Array,
default(){
return [1,2,3]
}
}
}
}
12、会自定义prop的验证吗?
export default{
props:{
propA:{
validator(val){
return 0 < val < 18
}
}
}
}
13、prop在default和validator函数中能用data和computed的数据吗?
不会,因为prop会在一个组件实例创建之前进行验证,所以data和computed在default或validator函数中是不可用的。
14、怎么传入一个对象的所有属性,不是传入一个对像
使用不带参数的 v-bind,例:对于一个给定的对象 post
post: {
id: 1,
title: 'My Journey with Vue'
}
<myComponent v-bind="post"></myComponent>
相当
<myComponent :id="post.id" :title="post.title"></myComponent>
三、组件的model选项
先举个例子,用v-model
来控制一个组件的显示隐藏
my_component.vue
<template>
<div v-show="value">
<span>我的组件</span>
<button @click="$emit('input',false)">隐藏</button>
</div>
</template>
<script>
export default{
props:{
value:{
type:Boolean,
default:false,
}
},
data(){
return{}
},
}
</script>
<template>
<div>
<myComponent v-model="show"></myComponent>
<button @click="show=true">显示</button>
</div>
</template>
<script>
export default{
data(){
return{
show:false,
}
},
components:{
myComponent:resolve =>require(['./my_component'],resolve),
}
}
</script>
那为什么这么写?不明白的可以看的不是很清楚,那么在看下面代码就清楚。
<template>
<div>
<myComponent :value="show" @input="show=$event"></myComponent>
<button @click="show=true">显示</button>
</div>
</template>
在父组件中用$event
访问通过$emit
抛出的值。
这么写是不是很清楚了,组件上的v-model='show'
相当 :value="show" @input="show=$event"
。
再举个例子
<template>
<div>
{{content}}
<myInput v-model="content"></myInput>
<!-- 等价于 -->
<myInput :value="content" @input="content=$event"></myInput>
</div>
</template>
<script>
export default {
data() {
return {
content: '',
}
},
components: {
myInput: resolve => require(['./my_input'], resolve),
}
}
</script>
my_input.vue
<template>
<input :value="value" @input="$emit('input',$event.target.value)"/>
</template>
<script>
export default{
props:['value'],
data(){
return{}
},
}
</script>
从以上代码可以看出,组件上的v-model='content'
相当 :value="content" @input="content=$event"
,
其组件的自定义事件必须是input
。
既然后可以实现v-model,那么model选项是用来干嘛的,
因为一个组件上的v-model 默认会利用名为value的prop和名为input的事件,
但是像单选框、复选框等类型的输入控件可能会将value 特性用于不同的目的,
model 选项可以用来避免这样的冲突。
那么上面的例子也可以这么实现
my_input.vue
<template>
<input :value="val" @input="$emit('change',$event.target.value)"/>
</template>
<script>
export default{
model:{
prop:'val',
event:'change'
},
props:['val'],
data(){
return{}
},
}
</script>
四、组件的插槽功能
1、插槽的写法
<myComponent>
通过插槽传进去的内容
</myComponent>
<template>
<div>
<h1>我的组件</h1>
<p>
<slot></slot>
</p>
</div>
</template>
页面渲染出
我的组件
通过插槽传进去的内容
如果 <myComponent>
没有包含一个 <slot></slot>
元素,则该组件起始标签和结束标签之间的任何内容都会被抛弃。
2、设置插槽的默认内容
<slot>默认内容</slot>
3、多个插槽使用(具名插槽)
<template>
<div>
<h2>默认插槽</h2>
<slot>默认内容</slot>
<h2>头部插槽</h2>
<slot name="header"></slot>
<h2>侧边栏插槽</h2>
<slot name="sider"></slot>
</div>
</template>
<template>
<div>
<myComponent>
我是默认插槽
<template v-slot:header>我是头部插槽</template>
<template #sider>
我是侧边栏插槽
</template>
</myComponent>
</div>
</template>
v-slot
指令的缩写是 #
,v-slot
只能添加在<template></template>
上
<slot></slot>
上面没有name属性,会自动渲染成<slot name="default"></slot>
,
也可以这么调用<template v-slot:default></template>
4、父组件中使用插槽时怎么访问子组件的数据
在实际项目中,我们要获取子组件的数据在父组件中渲染插槽的内容。怎么办?
首先要在子组件中给slot
绑定一个子组件的data中的一个对象childrenData,
例:<slot :toParent="childrenData"></slot>
topParent是给父组件中调用的,以上称为插槽prop。
然后在父组件中这样调用 <template v-slot:default="slotProps">{{slotProps.toParent}}</template>
,
其中slotProps是代表插槽prop的集合,其命名可以随便取的。此时slotProps.toParent的值
就是childrenData的值
下面是完整例子
<template>
<div>
<h2>默认插槽</h2>
<slot :toParent="childrenData" :toParent2="childrenData2">
{{childrenData.data1}}
{{childrenData2.data1}}
</slot>
<h2>头部插槽</h2>
<slot name="header" :headerData="headerData">
{{headerData.data1}}
</slot>
</div>
</template>
<script>
export default{
data(){
return{
childrenData:{
data1:'子组件数据一',
data2:'子组件数据二',
},
childrenData2:{
data1:'子组件数据三',
data2:'子组件数据四',
},
headerData:{
data1:'子组件头部数据一',
data2:'子组件头部数据二',
},
}
},
}
</script>
<template>
<div>
<myComponent>
<template v-slot:default="slotProps">
{{slotProps.toParent.data2}}
{{slotProps.toParent2.data2}}
</template>
<template v-slot:header="headerProps">
{{headerProps.headerData.data2}}
</template>
</myComponent>
</div>
</template>
当有子组件slot上有多个插槽prop时,父组件调用时候可以ES6对象解构的方法,例:
<template>
<div>
<myComponent>
<template v-slot:default="{toParent,toParent2}">
{{toParent.data2}}
{{toParent2.data2}}
</template>
<template v-slot:header="{headerData}">
{{headerData.data2}}
</template>
</myComponent>
</div>
</template>
也可以给子组件slot上绑定的值重新命名,例:
<template>
<div>
<myComponent>
<template #default="{toParent:a,toParent2:b}">
{{a.data2}}
{{b.data2}}
</template>
<template #header="{headerData:c}">
{{c.data2}}
</template>
</myComponent>
</div>
</template>
五、组件的非Prop特性
1、什么是组件的非Prop特性
在组件标签上添加的属性,没有在组件中props中定义,这些属性会被添加到这个组件的根元素上,这就是组件的非Prop特性,例:
<template>
<div data="">
<h2>我的组件</h2>
</div>
</template>
<template>
<div>
<myComponent data="noProp"></myComponent>
</div>
</template>
按F12打开调试工具可以看到

2、组件的非Prop特性的替换
如果组件的根节点已有data="children"
,那么在组件标签再定义data="noProp"
,这样根节点的data属性就会被替换。
按F12打开调试工具可以看到

3、组件的非Prop特性的合并
如果组件的根节点上有Class、Style属性,那么在组件标签再定义Class、Style,这样根节点的Class、Style是把两者合并后在赋值。例:
<template>
<div class="children" style="font-size:16px;">
<h2>我的组件</h2>
</div>
</template>
<template>
<div>
<myComponent class="parent" style="font-size:18px;color:red">
</myComponent>
</div>
</template>
按F12打开调试工具可以看到

4、怎么指定组件中某个元素继承非Prop特性的属性
用实例属性$attrs,包含了组件标签上不作为 prop被识别(且获取)的特性绑定(class 和 style 除外)。
在写基础组件中经常用到,下面举个基础输入框组件为例子:
<template>
<label>
{{ label }}
<input
v-bind="$attrs"
:value="value"
@input="$emit('input', $event.target.value)"
>
</label>
</template>
<script>
export default {
inheritAttrs: false,
props: ['label', 'value'],
data(){
return{}
},
mounted(){
console.log(this.$attrs)
}
}
</script>
<template>
<baseIinput class="input" style="color:red" v-model="val" placeholder="请输入" label="基础输入框"></baseInput>
</template>
<script>
export default {
data() {
return {
val:'',
};
},
components:{
baseInput:resolve => require(['./base_input.vue'],resolve)
}
};
</script>
例子中。mounted钩子函数打印出来是{placeholder:'请输入'}
,用v-bind
指令将 $attrs
绑定到要继承的元素上即可继承。

组件中的inheritAttrs选项为false时禁止组件的根节点继承非Prop特性,但不影响Class、Style


六、组件上监听根元素的原生事件
用v-on
的 .native
修饰符,来实现。例:监听输入框获取焦点事件
<baseInput @focus.native="onFocus"></baseInput>
但是你会发现,监听失败,也不产生任何报错。
这是因为这个组件的根元素是<label></label>
,而这标签是没有focus
这个事件。
你可以用实例属性$listeners
,其包含作用在这个组件上的所有监听器,配合v-on="$listeners"
将所有的事件监听器指向组件内部的任一个元素,如果改元素上还有其他监听器,可以用computed属性来合并监听器。
下面请看实例:
<template>
<label>
{{ label }}
<input v-bind="$attrs" v-bind:value="value" v-on="inputListeners">
</label>
</template>
<script>
export default {
props: ['label', 'value'],
data() {
return {}
},
computed: {
inputListeners: function(){
return Object.assign(
{},
// 组件标签上添加所有的监听器
this.$listeners,
//组件内部原来的监听器
{
input:(event) =>{
this.$emit('input',event.target.value)
}
}
)
}
}
}
</script>
<template>
<div>
{{val}}
<baseInput v-model="val" @focus="onFocus"></baseInput>
</div>
</template>
<script>
export default {
data() {
return {
val:'',
}
},
components: {
baseInput: resolve => require(['./base_input'], resolve),
},
methods:{
onFocus(){
console.log('获取到焦点')
}
}
}
</script>
七、component
标签和 is
特殊特性在组件上的应用
1、动态组件
<component :is="componentName"></component>
componentName
可以是在本页面已经注册的局部组件名和全局组件名,也可以是一个组件的选项对象。
当控制componentName
改变时就可以动态切换选择组件。
2、is的用法
有些HTML元素,诸如 <ul>、<ol>、<table> 和 <select>
,对于哪些元素可以出现在其内部是有严格限制的。而有些元素,诸如 <li>、<tr> 和 <option>
,只能出现在其它某些特定的元素内部。
<ul>
<card-list></card-list>
</ul>
所以上面<card-list></card-list>
会被作为无效的内容提升到外部,并导致最终渲染结果出错。应该这么写
<ul>
<li is="cardList"></li>
</ul>
八、组件的递归引用和组件的name选项
递归引用可以理解为组件调用自身,在开发多级菜单组件时就会用到,调用前要先设置组件的name选项。
注意一定要配合v-if使用,避免形成死循环
用element-vue组件库中NavMenu导航菜单组件开发多级菜单为例:
<template>
<el-submenu :index="menu.id" popper-class="layout-sider-submenu" :key="menu.id">
<template slot="title">
<Icon :type="menu.icon" v-if="menu.icon"/>
<span>{{menu.title}}</span>
</template>
<template v-for="(child,i) in menu.menus">
<side-menu-item v-if="Array.isArray(child.menus) && child.menus.length" :menu="child"></side-menu-item>
<el-menu-item :index="child.id" :key="child.id" v-else>
<Icon :type="child.icon" v-if="child.icon"/>
<span>{{child.title}}</span>
</el-menu-item>
</template>
</el-submenu>
</template>
<script>
export default{
name: 'sideMenuItem',
props: {
menu: {
type: Object,
default(){
return {};
}
}
}
}
</script>
九、父子组件的通信
1、父组件向子组件通信
通过props来通信
2、子组件向父组件通信
通过在父组件上自定义一个监听事件<myComponent @diy="handleDiy"></myComponent>
在子组件用this.$emit('diy',data)
来触发这个diy
事件,其中data为子组件向父组件通信的数据,
在父组件中监听diy
个事件时,可以通过$event
访问data这个值。
3、父组件访问子组件的实例或者元素
- 先用ref特性为子组件赋予一个ID引用
<baseInput ref="myInput"></<baseInput>
- 比如子组件有个focus的方法,可以这样调用
this.$refs.myInput.focus()
- 比如子组件有个value的数据,可以这样使用
this.$refs.myInput.value
- $refs 是在组件渲染完成之后生效,不是响应式的,所以要避免在模板和computed计算属性中使用
4、子组件访问父组件的实例或元素
用this.$parent
来访问
十、组件的依赖注入
上面写到,子组件可以用this.$parent
访问父组件的实例,孙子组件可以用this.$parent.$parent
来访问,那曾孙子组件呢,是不是要写很多个$parent
。
如果父组件下很多个子孙组件都要用祖先组件中的一个数据,这时候就要用到组件的依赖注入。
依赖注入,是通过provide
inject
这两选项实现,provide
是写在祖先组件中,inject
是写在需要注入的子孙组件中。
provide
选项应该是一个对象或返回一个对象的函数.
inject
选项应该是一个字符串数组或一个对象
对象的key是本地的绑定名,value是provide
的 key (字符串或 Symbol)。
value也可以是个一个对象,该对象的:
from 属性是provide
的 key (字符串或 Symbol)
default属性是在form定义的key在provide
中未定义,使用的默认值。
祖先组件
<template>
<div>
<son></son>
</div>
</template>
<script>
export default{
data(){
return {}
},
provide(){
return{
top:20,
}
},
components:{
son:resolve => require(['./son.vue'],resolve)
}
}
</script>
son儿子组件
<template>
<div>
<grandson></grandson>
</div>
</template>
<script>
export default{
data(){
return {}
},
inject:['top'],
components:{
grandson:resolve => require(['./grandson.vue'],resolve)
},
mounted(){
console.log(this.top)//20
}
}
</script>
grandson儿子组件
<template>
<div></div>
</template>
<script>
export default{
data(){
return {}
},
inject:{
top:{
from:'top',
default:'30'
},
bottom:{
from:'bottom',
default:'30'
}
},
components:{
grandson:resolve => require(['./grandson.vue'],resolve)
},
mounted(){
console.log(this.top)//20
console.log(this.bottom)//30
}
}
</script>
十一、.sync修饰符在组件中应用
通常在子组件中是不允许对props中的值进行修改,但是有些情况下,要对一个 prop 进行“双向绑定”。真正的双向绑定会带来维护上的问题,因为子组件可以修改父组件,且在父组件和子组件都没有明显的改动来源。
这时候我们可以用.sync修饰符来进行“双向绑定”,其原理是通过update:myPropName 的模式触发事件。
<template>
<div>
<button @click="show=true">显示</button>
<myComponent :show="parentShow" v-on:update:show="parentShow = $event"></myComponent>
</div>
</template>
<script>
export default{
data(){
return{
parentShow:false,
}
},
components:{
myComponent:resolve=>require(['./my_conponent'],resolve)
}
}
</script>
<template>
<div v-show="show">
<button @click="hide">隐藏</button>
</div>
</template>
<script>
export default{
props:{
show:{
type:Boolean,
default:false,
}
},
data(){
return{}
},
methods:{
hide(){
this.$emit('update:show',false)
}
}
}
</script>
为了方便起见,我们为这种模式提供一个缩写,即 .sync
修饰符:
<my-component :show.sync="show"></my-component>
注意带有 .sync 修饰符的 v-bind 不能和表达式一起使用,:show.sync="name=='小明'? true : false"
是无效的
将 v-bind.sync 用在一个对象上,例:v-bind.sync='{ title: doc.title }'
也是无效的。
<myComponent
:show.sync="info.show"
:name.sync="info.name"
:age.sync="info.age"
></myComponent>
如果遇到上述情况可以怎么这么写
<myComponent v-bind.sync="info"></myComponent>
这样会把 info 对象中的每一个属性 (如 show) 都作为一个独立的 prop 传进去,然后各自添加用于更新的 v-on 监听器。
十二、组件的render选项
render选项的值是一个函数,通常叫渲染函数。
该函数接收一个createElement的方法作为第一参数来创建VNode(虚拟DOM),并返回。
先举个全局组件的例子
Vue.component('myComponent', {
render: function (createElement) {
return createElement(
'h1',
this.title
)
},
props: {
title: {
type: String,
default: '我是渲染出来的组件'
}
}
})
以上组件调用后渲染出<h1>我是渲染出来的组件</h1>
在单页面组件中使用要去掉<template></template>
标签
<script>
export default {
props: {
title: {
type: String,
default: "我是渲染出来的组件"
}
},
data() {
return {};
},
render: function(createElement) {
return createElement("h1", this.title);
},
};
</script>
以上组件被调用后也渲染出<h1>我是渲染出来的组件</h1>
1、createElement函数的第一个参数,必填。
接收一个String类型的元素标签如'div'
,或者接收一个Function类型的函数返回元素标签;
总之第一个参数是必填项,接收一个元素标签。
2、createElement函数的第二个参数用来接收组件的数据对象,类型为Object,可选。
-
class项,绑定ClassName,对应
:class
,值为一个字符串、对象或字符串和对象组成的数组,例:值为对象:
data(){ classA:true }, render: function (createElement) { return createElement('div', { class:{'classA':this.classA}, }, '我是渲染出来的组件', ); },
值为字符串:
render: function (createElement) { return createElement('div', { class:'classA', }, '我是渲染出来的组件', ); },
值为字符串和对象组成的数组
data() { return { classB:true }; }, render: function (createElement) { return createElement('div', { class:['classA',{'classB':this.classB}], }, '我是渲染出来的组件', ); },
-
style项,绑定style,对应
:style
,接受一个字符串、对象,或对象组成的数组,例:值为字符串
render: function (createElement) { return createElement('div', { style:'color:red' }, '我是渲染出来的组件', ); },
值为对象
render: function (createElement) { return createElement('div', { style:{ color:'red', fontSize: '14px' } }, '我是渲染出来的组件', ); },
值为对象组成的数组
render: function (createElement) { return createElement('div', { style:[ { color:'red', }, { fontSize: '14px' } ] }, '我是渲染出来的组件', ); },
-
attrs项,普通的HTML特性,如id、title特性,例:
render: function (createElement) { return createElement('div', { attrs:{ id:'idA', title:'我是渲染出来的组件' } }, '我是渲染出来的组件', ); },
渲染出
<div id="idA" title="我是渲染出来的组件">我是渲染出来的组件</div>
-
domProps项,DOM属性,如innerHTML,列:
render: function (createElement) { return createElement('div', { domProps: { innerHTML: 'DOM属性内容' } }, '我是渲染出来的组件', ); },
渲染出
<div>DOM属性内容</div>
我是渲染出来的组件 -
props项,若createElement函数的第一个参数是组件的话,且组件内有props属性,props选项相当给组件内的props传值
Vue.component('myTitle', { render: function (createElement) { return createElement( 'h1', this.title ) }, props: { title: { type: String, default: '我是渲染出来的组件' } } }) Vue.component('myComponent', { render: function (createElement) { return createElement( 'myTitle', { props: { title: '我是props传递出来的组件' } }, ) }, })
<myComponent></myComponent>
会渲染出来
<h1>我是props传递出来的组件</h1>
-
on项,事件监听器,其它用法和
v-on
一样,例:render: function (createElement) { return createElement('div', { on: { click: function(e){ console.log(e) }, } }, '我是渲染出来的组件', ); },
-
nativeOn项,只用于第一个参数为组件标签时,用来监听组件内根节点上的事件,列:
components:{ myComponent:resolve =>require(['./my_component'],resolve) }, render: function (createElement) { return createElement('myComponent', { nativeOn: { click: function(e){ console.log(e) } } }, ); },
-
scopedSlots项,在父组件中用子组件的数据渲染插槽内容,例:
子组件
<template> <div> <slot :title="title"></slot> <slot name="content" :content="content"></slot> </div> </template> <script> export default { data() { return { title: '我的组件', content: '内容' } }, } </script>
父组件
components: { myComponent: resolve => require(['./my_component'], resolve) }, render: function (createElement) { return createElement('myComponent', { scopedSlots: { default: ({ title }) => createElement('span', title), content: ({ content }) => createElement('p', content) } }, ); },
-
slot项,如果组件是其它组件的子组件,需为插槽指定名称(demo待续);
-
directives项,自定义指令(demo待续);
-
key项,定义组件的key(demo待续);
-
ref项,给组件增加ref属性,通过
this.$refs
,在render函数组件中调用其子组件的方法,例:子组件
<template> <div>{{title}}</div> </template> <script> export default { data() { return { title: '我的组件', } }, methods:{ show(){ console.log(this.title) } } } </script>
render函数组件
<script> export default { data() { return {}; }, components: { myComponent: resolve => require(['./my_component'], resolve) }, render: function (createElement) { var myChild = createElement('myComponent', { ref: 'myRef' }); const _this = this; return createElement('div', { on: { click: function () { _this.$refs.myRef.show();//我的组件 } }, ref: 'myRef', }, [myChild]) }, }; </script>
3、createElement函数的第三个参数用来接收子级虚拟节点,可选。
-
类型可以是String,可以是文字类型的节点,例:
render: function (createElement) { return createElement('div', '我是渲染出来的组件'); },
或者
<script> export default { props: { title: { type: String, default: "我是渲染出来的组件" } }, data() { return {}; }, render: function (createElement) { return createElement('div', this.title); }, }; </script>
-
类型可以是Array,其中值可为字符串、createElement函数,例:
<script> export default { props: { title: { type: Function, default: "我是渲染出来的组件" } }, data() { return {}; }, render: function (createElement) { return createElement('div', [this.title,'我是字符串',createElement('h1','我是子节点')]); }, }; </script>
-
利用
this.$slots
在render函数组件中实现插槽功能,例:子组件
<script> export default { data() { return { }; }, render: function (createElement) { return createElement( 'div', [ createElement('h1',this.$slots.default), createElement('h2',this.$slots.header) ] ) }, }; </script>
父组件
<template> <div> <myRender> 父默认内容 <template v-slot:header>父内容</template> </myRender> </div> </template> <script> export default { data() { return {} }, components: { myRender: resolve => require(['./my_render.vue'], resolve), }, } </script>
渲染出
<div> <h1>父默认内容</h1> <h2>父内容</h2> </div>
-
利用
this.$scopedSlots
实现调用render函数组件时用render函数组件中的数据编辑插槽,例:子组件
<script> export default { data() { return { info:'我是render函数组件' }; }, render: function(createElement) { return createElement("div", [ this.$scopedSlots.header({ title: this.info }) ]); } }; </script>
父组件
<template> <div> <myRender> <template v-slot:header="{title}">{{title}}</template> </myRender> </div> </template> <script> export default { data() { return {}; }, components: { myRender: resolve => require(["./my_render.vue"], resolve) } }; </script>
-
组件树中的所有 VNode 必须是唯一的,所以当参数类型为Array时,每个值不可重复,例:
render: function (createElement) { var myChild = createElement('p', 'hi') return createElement('div', [ // 错误 - 重复的 VNode myChild,myChild,myChild ]) },
应该这么做
render: function (createElement) { return createElement('div', Array.from({ length: 20 }).map(function () { return createElement('p', 'hi') }) ) }
4、render函数中实现v-if
v-else
v-for
模板功能,例:
<ul v-if="lists.length">
<li v-for="item in lists">{{ item.name }}</li>
</ul>
<p v-else>没有数据</p>
props: ['lists'],
render: function (h) {
if (this.lists.length) {
return h('ul', this.lists.map(function (item) {
return h('li', item.name)
}))
} else {
return h('p', '没有数据')
}
}
5、render函数中实现v-model
模板功能,例:
props: ["value"],
render: function(h) {
var _this = this;
return h("input", {
domProps: {
value: _this.value
},
on: {
input: function(event) {
_this.$emit("input", event.target.value);
}
}
});
}
6、在父组件中使用render自定义子组件
在使用iview中Tree组件,你会发现组件提供了render属性让你可以自定义树节点。那是怎么实现的,下面举个简单列子:
子组件
<script>
export default {
props: {
render: {
type: Function,
},
data: {
type: Object,
default(){
return {}
},
}
},
render: function (h) {
const params = {
data: this.data,
};
return this.render(h,params);
},
};
</script>
父组件
<template>
<div>
<myRender :render="this.render" :data="this.data">
</myRender>
</div>
</template>
<script>
export default {
data() {
return {
render: function(h,{data}) {
const info= `我是${data.name},今年${data.age}岁`;
return h('h1', info);
},
data:{
name:'小明',
age:18,
},
}
},
components: {
myRender: resolve => require(['./my_render.vue'], resolve),
},
}
</script>
渲染出
我是小明,今年18岁
注意 render值不能用箭头函数,否则this指向会出错。
7、函数式组件的应用
-
什么是函数式组件,满足以下几个特点可以称为函数式组件
- 没有管理任何状态
- 没有监听任何传递给它的状态
- 没有生命周期方法
- 只是接收一些prop的函数
-
函数式组件优点,渲染开销低
-
怎么使用函数式组件
将
functional
选项标记为true
,此时组件成为- 无状态 == 无响应式数据
- 无实例 == 无this上下文
那么上个例子的子组件可以这么写
<script> export default { functional:true, render: function (h,context) { const params = { data: context.props.data, }; return context.props.render(h,params); }, }; </script>
组件需要的一切都是通过 context 参数传递,它是一个包括如下字段的对象:
- props:提供所有 prop 的对象
- children: VNode 子节点的数组
- slots: 一个函数,返回了包含所有插槽的对象
- scopedSlots: 一个暴露传入的作用域插槽的对象。也以函数形式暴露普通插槽
- data:传递给组件的整个数据对象,作为 createElement 的第二个参数传入组件
- parent:对父组件的引用
- listeners: 一个包含了所有父组件为当前组件注册的事件监听器的对象。这是 data.on 的一个别名
- injections: 如果使用了 inject 选项,则该对象包含了应当被注入的属性
在模板中使用函数式组件这样声明:
<template functional> <div> <p v-for="item in props.lists" @click="props.listClick(item);"> {{ item }} </p> </div> </template>