要点概览
- props参数
- event自定义事件
- slot定制插槽
- 组件通信
组件封装及使用
1.建立组件的模板,先把架子搭起来,写写样式,考虑好组件的基本逻辑。
2.准备好组件的数据输入。即分析好逻辑,定好 props 里面的数据、类型。
3.准备好组件的数据输出。即根据组件逻辑,做好要暴露出来的方法。
4.封装完毕了,引用(全局、局部),注册,使用组件
import FormDialog from '@/components/dialog/formDialog'
components: {
FormDialog
},
<FormDialog> </FormDialog>
好处
-
提高代码复用性和开发效率
-
减少代码冗余
1、props,组件属性
很多时候,需要外界传递数据给组件,使其根据不同数据,不同的场景,渲染不同样式。
通过props,数据可从 父组件传入子组件。
1.1 给组件添加属性
命名格式,camelCase
一般写法,只提供属性名
props:['属性1', '属性2', '属性3', ...]
如:
<script>
export default {
props:['lon','lat'],
}
</script>
具体写法,指定类型,是否必传,默认值 等
props: {
// 单类型
propA: Number,
// 多种类型
propB: [String, Number],
// 必传
propC: {
type: String,
required: true,
},
// 有默认值
propD: {
type: Number,
default: 100,
},
// 数组/对象类型,默认值 由一个工厂函数返回
propE: {
type: Object,
default: function () {
return { message: 'hello' };
},
},
// 自定义验证函数
propF: {
validator: function (value) {
return value > 10;
},
},
},
添加完属性,就可以又父组件给它赋 属性值
<ChildView propA="标题" :propB="fatherData"/>
单向绑定原则
所有的 props ,都因父组件的更新而变化,自然地将新的状态向子组件传递,而不能逆向传递。
在大多数场景下,子组件应该抛出一个事件来通知父组件做出改变。
props传过来的数据只做展示,不得修改,想修改,再新写一个data中的变量承接做数据的再处理。
深浅拷贝
当props的类型是对象或数组时,虽然子组件更改 props 值,仍然可以更改父组件的数据。这是因为 JavaScript 的对象和数组是按引用传递。
inheritAttrs属性
组件的根元素是否继承特性
2、事件传递
子组件触发一个事件,往往需要 传递给父组件,交给父组件来处理相关逻辑
$emit
实现:子组件可以通过调用内置的 $emit 方法,通过传入事件名称来抛出一个事件,$emit第二个参数为传递的数值,可用于传参
// 子组件中的按钮
<button @click="$emit('onClick')">点击</button>
// 父组件调用子组件
<child @onClick="handleClick" />
传参
通过事件的传递,实现了 子组件可将某些参数向父组件传递。解决了props的局限
// 子组件方法
clickBtn(){
// 触发父组件方法,并传递参数data到父组件
this.$emit('onClick', 1)
this.$emit('onClick', {tab: 'xxxx'})
}
// 父组件调用子组件
<child @onClick="handleClick" />
// ... ...
// 父组件中被触发的方法,接受到子组件传来的参数
handleClick(val){
// val 为传过来的值
}
可以添加校验
有些场景,在子组件中,也可以把校验相关的代码封装进去
父组件中的逻辑要放在父组件处理,子组件基于父组件的数据做的逻辑放在子组件中处理; 这样既降低了耦合性,也保证了各自的数据不被污染。
// 子组件中
export default {
emits: {
// 没有校验
click: null,
// 校验 submit 事件
submit: ({ email, password }) => {
if (email && password) {
return true
} else {
// 无效
return false
}
}
},
methods: {
clickBtn(email, password) {
this.$emit('submit', { email, password })
}
}
}
3、slot 插槽
3.1 背景
使用props父向子传递数据,传递的数据都是对象、数组、字符串等"js类型的数据"。有时候我们想要传递大量的html类型的片段。
一个通用组件,往往不能够完美的适应所有应用场景 所以在封装组件的时候,只需要完成组件 80% 的功能,剩下的 20% 让父组件通过 solt 解决。
只需要在合适的位置留一个 slot,将按钮的位置留出来,然后在父组件写入按钮。
组件化编程中,css不怎么需要传递,因为我们可以通过深度作用域选择器,如/deep/去在父组件中选中子组件中的dom元素去设置样式
3.2 分类
- 默认插槽
又叫:普通插槽、单个插槽,匿名插槽,即不带名字的,不用设置name属性 这样的
<template>
<div class="box">
<h1>我是子组件</h1>
<slot></slot>
<!-- 子组件写了一个这样的slot标签,才能接收到父组件传递过来的html片段。不写的话父组件传了也是白传即不会生成DOM,也不会渲染
-->
</div>
</template>
<template>
<div id="app">
<child>
<div slot="default">我是插槽内容</div> //slot="default"可以省略
</child>
</div>
</template>
- 具名插槽
带个名字的 <slot name="footer"></slot>这样的,拥有name属性
应用场景,放置在特定位置的插槽
<template>
<div id="app">
<child>
<div slot="header">我是插槽内容</div> //slot="header"不可以省略
</child>
</div>
</template>
写法二,模板标签不会渲染成DOM
<template>
<div id="app">
<child>
<template slot="header"> // 简写成<template #header>
<p>模板标签头部</p>
</template>
</child>
</div>
</template>
- 作用域插槽
插槽的略微高级点用法,比较少用到
作用域插槽是带数据的插槽,子组件提供给父组件的参数,父组件根据子组件传过来的插槽数据来进行不同的展现和填充内容。在标签中通过slot-scope来接受数据。
slot 中可以作为一个作用域,在子组件中定义变量,然后在父组件中自定义渲染的方式
<template>
<div class="">
<slot :user="user"> </slot> //:user="user",就是插槽所携带的数据
</div>
</template>
<script>
export default {
data() {
return {
user: {
name: "周星驰",
age: 50,
},
};
},
};
</script>
在父组件中通过slot-scope来接受子组件提供的参数
<template>
<div class="father">
<child>
<template slot-scope="{ user }">
<div>{{user.name}}</div>
<div>{{user.age}}</div>
</template>
</child>
</div>
</template>
插槽内容无法访问子组件的数据。
4、实现 双向绑定 v-model
在组件上 使用,以实现属性的双向绑定
<!-- CustomInput.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue']
}
</script>
<template>
<input
:value="modelValue"
@input="$emit('update:modelValue', $event.target.value)"
/>
</template>
使用
<CustomInput v-model="editText" />
另一个实现方式:
<!-- CustomInput.vue -->
<script>
export default {
props: ['modelValue'],
emits: ['update:modelValue'],
computed: {
value: {
get() {
return this.modelValue
},
set(value) {
this.$emit('update:modelValue', value)
}
}
}
}
</script>
<template>
<input v-model="value" />
</template>
4、组件之间的其他通信方式
4.1 children
子实例可以用 this.$parent 访问父实例,子实例被推入父实例的 $children 数组中。
// 父组件
data() {
return {
msg:'age'
}
},
components: {
Hear,//子组件
},
methods: {
test() {
alert(1)
}
},
// 子组件里
methods: {
click() {
console.log(this.$parent.msg);//访问 父组件的数据
this.$parent.test() //调用 父组件的方法
}
}
二
在父组件中使用 this.$children 他得到的是一个数组[VueComponent, VueComponent],数组中的值就是每一个组件实例。
节制地使用 children , 它们的主要目的是作为访问组件的应急方法。更推荐用 props 和 events 实现父子组件通信
4.2 ref
通过this.$refs, 可以访问到此vue实例中的所有设置了ref属性的DOM元素,并对其进行操作。
ref='xx'相当于id='xx'
this.$refs.xx去找具体的位置
<Children ref="foo" />
this.$refs.foo // 获取子组件实例,通过子组件实例就能拿到对应的数据
4.3 listeners
利用 $attrs 实现祖孙组件间的数据传递, $listeners 实现祖孙组件间的事件监听
$attrs
会继承所有的父组件属性(除了 prop 传递的属性、class 和 style ),一般用在子组件的子元素上。可以通过 v-bind="$attrs" 传入内部组件。在创建高级别的组件时非常有用。
$ listeners
它是一个对象,里面包含了作用在这个组件上的所有监听器,你就可以配合 v-on="$listeners" 将所有的事件监听器指向这个组件的某个特定的子元素。(相当于子组件继承父组件的事件)
inheritAttrs: 为true 继承除 props 之外的所有属性;为false 只继承 class style 属性
其他
Bus
使用场景:兄弟组件传值。
1.创建一个中央时间总线EventBus
2.兄弟组件通过emit第二个参数为传递的数值
3.另一个兄弟组件通过$on监听自定义事件
provide / inject
在祖先组件定义provide属性,返回传递的值。在后代组件通过inject接收组件传递过来的值
provide(){
return(){
foo: 'xxxxxx'
}
inject:['foo'] // 获取到祖先组件传递过来的值