1、封装组件的目的
把业务逻辑相同,高度重复的代码封装起来,为了提高代码的复用,减少代码的冗余。
2、组件使用
(1)引用
import navs from '@/views/nav/index'
(2)注册
components:{
'v-nav':navs
}
(3)使用
< v-nav> < /v-nav>
3、组件三要素
属性:props属性 ,inheritAttrs属性
事件:event
插槽:slot
(1)属性:
数据从父组件传到子组件props属性
父对子传参,就需要用到 props,通常的 props 是这样的:
props:[‘data’,‘type’]
但是通用组件的的应用场景比较复杂,对 props 传递的参数应该添加一些验证规则,常用格式如下:
props:{
// 基础类型检测 (`null` 意思是任何类型都可以)
propA: Number,
// 多种类型
propB: [String, Number]
// 必传且是字符串
propC: {
type: String,
required: true //必须传
},
// 数字,有默认值
propD: {
type: Number,
default: 100 //default默认值,默认是100
},
// 数组/对象的默认值应当由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' }
}
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10
}
}
inheritAttrs属性
vue官网对于inheritAttrs的属性解释:如果你不希望组件的根元素继承特性,你可以在组件的选项中设置 inheritAttrs: false。
举个例子来验证一下。
父组件 parent-component.vue
<template>
<div class="parent">
<child-component aaa="1111"></child-component>
</div>
</template>
<script>
import ChildComponent from './child-component'
export default {
components: {
ChildComponent
}
}
</script>
子组件 child-component.vue 设置 inheritAttrs: true(默认)
<template>
<div class="child">子组件</div>
</template>
<script>
export default {
inheritAttrs: true,
mounted() {
console.log('this.$attrs', this.$attrs)
}
}
</script>
最后渲染的结果:
Elements
Console
子组件 child-component.vue 设置 inheritAttrs: false
<template>
<div class="child">子组件</div>
</template>
<script>
export default {
inheritAttrs: fasle,
mounted() {
console.log('this.$attrs', this.$attrs)
}
}
</script>
最后渲染的结果:
总结:
由上述例子可以看出,前提:子组件的props中未注册父组件传递过来的属性。
1.当设置inheritAttrs: true(默认)时,子组件的顶层标签元素中(本例子的div元素)会渲染出父组件传递过来的属性(本例子的aaa=“1111”)。
2.当设置inheritAttrs: false时,子组件的顶层标签元素中(本例子的div元素)不会渲染出父组件传递过来的属性(本例子的aaa=“1111”)。
3.不管inheritAttrs为true或者false,子组件中都能通过$attrs属性获取到父组件中传递过来的属性。
(2)events:子组件触发父组件事件
在通用组件中,通常会需要有各种事件,比如复选框的 change 事件,或者组件中某个按钮的 click 事件,有时子组件需要触发一个事件,并传递给父组件。
// 子组件方法:触发父组件方法,并传递参数data到父组件
handleSubmit(data){
this.$emit('submitToParent', data)
}
// 父组件调用子组件
<child-component @submitToParent="parentSubmit"></child-component>
... ...
// 父组件中被触发的方法,接受到子组件传来的参数
parentSubmit(data){
// 父组件的逻辑处理
}
父组件中的逻辑要放在父组件处理,子组件基于父组件的数据做的逻辑放在子组件中处理; 这样既降低了耦合性,也保证了各自的数据不被污染
(3)留一个 slot
一个通用组件,往往不能够完美的适应所有应用场景 所以在封装组件的时候,只需要完成组件 80% 的功能,剩下的 20% 让父组件通过 solt 解决。
例如:
上面是一个通用组件,在某些场景中,右侧的按钮是 “处理” 和 “委托”。在另外的场景中,按钮需要换成 “查看” 或者 “删除” 在封装组件的时候,就不用写按钮,只需要在合适的位置留一个 slot,将按钮的位置留出来,然后在父组件写入按钮。
子组件
<div class="child-btn">
<!-- 具名插槽 -->
<slot name="button"></slot>
<!-- 匿名插槽(每个组件只能有一个) -->
<slot><slot>
</div>
父组件
<child>
<!-- 对应子组件中button的插槽 -->
<button slot="button">slot按钮</button>
</child>
开发通用组件的时候,只要不是独立性很高的组件,建议都留一个 slot,即使还没想好用来干什么。
开发过程中,常常需要在子组件内添加新的内容,这时候可以在子组件内部留一个或者多个插口
(4)子组件改变父组件的数据
当我们把父元素的数据给子组件时,要传一个非基础类型,即传递对象or数组,子组件通过访问对象中的属性操作数据,因为对象和数组是传引用,所以在子组件中修改的时候,父组件也会同步改变,如下:
// 父组件要props传递给子组件的数据
data:{
info:'父组件信息'
}
// 子组件
<template id="tpl">
<div>
<button @click="change">change</button>
<p>{{data.info}}</p>
</div>
</template>
... 省略部分无关代码 ...
props:['data'],
methods:{
change(){
this.data.info = 'change info'
}
}
当子组件点击change按钮改变数据的时候,父组件也会同步改变
(5)、vue组件封装v-model
首先来理解下 v-model
<input v-model="something">
<input
v-bind:value="something"
v-on:input="something = $event.target.value">
与组件一起使用时,简化为
<custom-input
:value="something"
@input="value => { something = value }">
</custom-input>
所以对于一个组件来说v-model,它应该
1.接受value道具
2.用新值发出一个input事件
一个组件上的 v-model 默认会利用名为 value 的 prop 和名为 input 的事件,但是像单选框、复选框等类型的输入控件可能会将 value 特性用于不同的目。model 选项可以用来避免这样的冲突:
<template>
<input
type="checkbox"
v-bind:checked="checked"
v-on:change="$emit('change', $event.target.checked)"
>
</template>
<script>
export default {
name: 'base-checkbox',
model:{
prop: 'checked',
event: 'change'
},
props: {checked: Boolean}
}
现在在这个组件上使用 v-model 的时候:
<base-checkbox v-model="lovingVue"></base-checkbox>
这里的 lovingVue 的值将会传入这个名为 checked 的 prop。同时当 触发一个 change 事件并附带一个新的值的时候,这个 lovingVue 的属性将会被更新。
我们来继续看嵌套组件时怎么再封装一个v-model,我们新建一个组件special-checkbox里面包含上面的组件base-checkbox,如何将子父组件的v-model打通
<template>
<base-checkbox v-model="newlovingVue"></base-checkbox>
</template>
<script>
export default {
name: 'special-checkbox ',
model:{
prop:'lovingVue',//要存在于proops
event:'change'//当组件的值发生改变时要emit的事件名
},
props: ['lovingVue'],
data:function(){
return{//要重新定义一个data,赋值为props中的值,因为组件时单数据流,不能直接修改props
newlovingVue:this.lovingVue
}
},
watch:{//这里检测data中的值,一旦发生变化就提交事件到父组件
newlovingVue:function(newVal,oldVal){
this.$emit('change',newVal)
}
}
}
</script>
现在使用这个二次封装的组件的时候
<special-checkbox v-model="lovingVue"></base-checkbox>
(6)、子组件发射事件,父组件接收事件
自组件通过this.$emit(“inChange”,index)进行发射事件,inChange为事件名称,index为参数。
<template>
<div class="child">
<v-switch v-model="item.in_switch" :label="item.in_switch? 'on':'off' " @change="inChange(index)"></v-switch>
</div>
</template>
<script>
export default {
props:{
item: Object,
index: Number
},
methods:{
inChange(index){
// 父组件监听 inChange事件,将index传过去,将处理逻辑放在父组件
this.$emit("inChange",index);
}
}
}
</script>
父组件接收
<template>
<div class="parent">
// 注意这里inChange的参数可有可无
<child :item="item" :index="index" @inChange="inChange" />
</div>
</template>
import child from "../child";
export default {
components: {
child
},
data() {
return {
item: {
in_switch: true,
},
index: 0
}
},
methods:{
// inchange事件处理,有index参数
inChange(index){
console.log( index)
},
}
}
</script>