以脚手架搭建的Vue
项目为笔记背景。
如果将所有的代码逻辑全部放到一个组件中,代码是非常的臃肿和难以维护的。
并且在真实开发中,可能会有更多的内容和代码逻辑,对于扩展性和可维护性来说都是非常不友好的。
所以开发者,我们会把一个大功能进行拆分,拆分成一个个功能的小组件。
那组件多了,就必然涉及到组件之间的数据传递。
父组件有一些数据,需要子组件来进行展示。这种情况可以通过props
组合式API
来完成组件之间的通信。
props
可以在当前组件上注册一些自定义的attribute
(属性)。
父组件给这些attribute
赋值,子组件通过attribute
的名称获取到对应的值。
props
有两种常见的用法字符串数组,数组中的字符串就是attribute
的名称
props: ['name', 'age']
对象类型,对象类型可以在指定attribute
名称时,指定它的类型、是否是必须的、默认值等等
props: {
name: {
type: String, // 需要传递的数据类型
required: true, // 是否必须传递,不传递的话报错
default: "张三" // 属性默认值
}, // 一般required和default只选其一配置即可
// 对象类型的默认值,必须是一个函数,使用函数返回值来确定默认值
friend: {
type: Object,
default() {
return { name: "james" }
}
},
hobbies: {
type: Array,
default: () => ["篮球", "rap", "唱跳"]
},
}
如何选择?
数组类型它对传入的attribute
的名称,并不能对其进行任何限制吗,而对象的写法则让props
变得更加完善。
当使用对象语法的时候,我们可以对传入的内容作出很多限制:
attribute
的类型;attribute
是否是必传的;attribute
的默认值;这些限制可以让程序更加严谨。通常都会选用这种方式去定义props
的相关属性。
注意点:
required
和default
只选其一配置即可String
Number
Boolean
Array
Object
Date
Function
Symbol
由于HTML
中的 attribute
名是大小写不敏感的,所以浏览器会把所有大写字符解释为小写字符。
因此使用 DOM
模板时,驼峰命名法的 prop
名可以使用其等价的 短横线分隔命名方式来使用。
也可以直接使用驼峰命名法来使用。
定义时:
props: {
showMessage: {
type: String,
default: "默认showMessage的值"
}
}
使用时:
<show-info :age="100" showMessage="我的个人信息"/>
<show-info :age="100" show-message="我的个人信息"/>
更推荐使用时,用短横线分隔的方式来使用。
当我传递给一个组件某个属性,该属性并没有定义对应的props或者emits时,就称之为 非props
的Attribute
。
常见的包括class
、style
、id
等属性 。
这时有两种情况:
当组件有单个根节点时,非props
的Attribute
将自动添加到根节点的标签属性中。
如果组件有多个根节点,那么会报警告,必须手动的指定要绑定到哪一个属性上。
手动指定的方式是使用$attrs
来获取非props
的Attribute
<template>
<div class="infos">
<h2 :class="$attrs.class">姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
</div>
</template>
上面的情况就叫做属性继承。
如果不希望组件的根元素继承attribute,可以在组件中设置 :
export default {
inheritAttrs: false
}
上面设置可以禁用Attribute
继承和多根节点的方式。
<template>
<!-- 展示个人信息的组件 -->
<!-- 如果当前的属性是一个非prop的attribute, 那么该属性会默认添加到子组件的根元素上 -->
<show-info name="jack"
:age="25"
:height="1.78"
address="广州市"
abc="cba"
class="active" />
</template>
<script>
// 导入子组件
import ShowInfo from './ShowInfo.vue'
export default {
// 局部注册子组件
components: {
ShowInfo
}
}
</script>
<style scoped>
</style>
<template>
<div class="infos">
<!-- 获取继承而来的class属性 -->
<h2 :class="$attrs.class">姓名: {{ name }}</h2>
<h2>年龄: {{ age }}</h2>
<h2>身高: {{ height }}</h2>
<h2>Message: {{ showMessage }}</h2>
</div>
<!-- v-bind="$attrs 一次性把所有继承而来的属性全部绑定到当前元素上面 -->
<div class="others" v-bind="$attrs"></div>
</template>
<script>
export default {
//inheritAttrs: false, // 设置不允许属性继承
props: { // props对象语法定义属性
name: {
type: String,
default: "我是默认name"
},
age: {
type: Number,
required: true
},
height: {
type: Number,
default: 1.75
},
// 对象类型写默认值时, 需要编写default的函数, 函数返回默认值
friend: {
type: Object,
default() {
return { name: "james" }
}
},
hobbies: {
type: Array,
default: () => ["篮球", "rap", "唱跳"]
},
showMessage: {
type: String,
default: "我是showMessage"
}
}
}
</script>
<style scoped>
</style>
当子组件有一些事件被触发时,比如在组件中发生了点击,父组件需要作出一定的响应;
子组件有一些内容想要传递给父组件的时候;
在子组件中定义好在某些情况下触发的事件名称(自定义事件);
在父组件中以v-on
的方式传入要监听的事件名称,并且绑定到对应的方法中;
在子组件中发生某个事件的时候,根据事件名称触发对应的事件;
子组件:
<template>
<div class="add">
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
</div>
</template>
<script>
export default {
methods: {
btnClick(count) {
// 当btnClick方法被调用时,子组件会发出去一个自定义事件
// 第一个参数自定义的事件名称
// 第二个参数是传递的参数
this.$emit("add", count)
}
}
}
</script>
父组件:
<template>
<div class="app">
<h2>当前计数: {{ counter }}</h2>
<!-- 并且监听组件add-counter内部的add事件 -->
<add-counter @add="addBtnClick"></add-counter>
</div>
</template>
<script>
//导入组件
import AddCounter from './AddCounter.vue'
export default {
// 注册组件
components: {
AddCounter,
},
data() {
return {
counter: 0
}
},
methods: {
// 子组件中add事件触发时,将调用此方法
addBtnClick(count) {
this.counter += count
},
}
}
</script>
简单来说:
父组件内, 在子组件标签上添加 @自定义事件="父methods函数"
子组件内, 恰当时机this.$emit('自定义事件名', 值)
假设开发父组件和子组件的是两个人,那么开发父组件的人在使用子组件时。
一时是找不到子组件中发出的自定义事件在哪里的。
因为这都是写在业务逻辑里,代码量比较多的时候确实比较难找。而且这些事件也缺乏有效的管理。
因此Vue3.x
提供了emits
组合式API
的写法去优化上面的内容。
<template>
<div class="add">
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
</div>
</template>
<script>
export default {
// emits数组语法(把发出的自定义事件全部写到这里面)
// 相当于把发出的事件进行注册说明一下,这个写法是非必须的
emits: ["add"],
methods: {
btnClick(count) {
this.$emit("add", count)
}
}
}
</script>
emits
数组写法有两个好处
vscode
开发的话,还可以获得事件提示,对开发更加友好,如果不写则没有事件提示<template>
<div class="add">
<button @click="btnClick(1)">+1</button>
<button @click="btnClick(5)">+5</button>
<button @click="btnClick(10)">+10</button>
</div>
</template>
<script>
export default {
// emmits对象语法,对参数count进行验证
emits: {
add: function(count) {
if (count <= 10) {
return true
}
return false
}
},
methods: {
btnClick(count) {
console.log("btnClick:", count)
this.$emit("add", 100)
}
}
}
</script>
对象语法在写业务逻辑的时候用的可能会少一些,一般只用数组语法来进行优化就可以了。
除非是封装一些严格的组件库的时候,可以对事件参数作出相应的验证。
Provide/Inject
用于非父子组件之间共享数据。这也是Vue3
新提供的一些特性。
如果有些深度嵌套的组件,子组件想要获取父组件的部分内容。
在这种情况下,如果仍然将props
沿着组件链逐级传递下去,就会非常的麻烦。
此时可以使用 Provide
和 Inject
,无论层级结构有多深,父组件都可以作为其所有子组件的依赖 提供者。
父组件有一个 provide
选项来提供数据,子组件有一个 inject
选项来开始使用这些数据。
父组件不需要知道哪些子组件使用它使用了它提供的属性。 子组件也不需要知道 注入的属性来自哪里。
A组件:
<template>
<div class="app"></div>
</template>
<script>
import Home from './B.vue'
export default {
components: {
B
},
// provide一般都是写成函数
provide() {
return {
name: "张三",
age: 25
}
}
}
</script>
父组件提供一个对象数据。
B组件:
<template>
<div class="app"></div>
</template>
<script>
import Home from './C.vue'
export default {
components: {
C
},
}
</script>
C组件:
<template>
<div class="banner">
<h2> {{ name }} - {{ age }}</h2>
</div>
</template>
<script>
export default {
inject: ["name", "age"]
}
</script>
C
组件并不是A
组件的直接子组件。但是可以使用inject
的方式接收到。
今后开发Vue3项目,可能这种方式用的不多,因为可以使用
Vuex
或者pinia
去共享数据。
Vue3
从实例中移除了 $on
、$off
和 $once
方法。
也就是说我们无法像Vue2
中那种方式去使用事件总线。也不能直接把公共Vue
所以我们如果希望继续使用全局事件总线,可以通过 Vue3
官方有推荐一些库。
mitt
或者tiny-emitter
。 这里就以mitt
为例。
num install mitt -s
import mitt from 'mitt'
const emitter = mitt()
export default emitter
<template>
<div>
<h1>A组件</h1>
<button @click>
按钮
</button>
</div>
</template>
<script>
import emitter from '../plugins/event-bus.js'
export default {
created(){
emitter.emit("fn",{name:'张三',age:19})
}
}
</script>
<template>
<div>
<h1>B组件</h1>
{{ str }}
</div>
</template>
<script >
import emitter from '../plugins/event-bus.js'
export default {
created(){
// 监听fn事件
emitter.on("fn",msg=>{
console.log(msg);
});
},
methods: {
eventHandler() {
console.log("已经终止事件监听")
}
},
unmounted() {
// 取消监听fn函数
eventBus.off("fn", this.eventHandler)
}
}
</script>
按照更加严谨的做法,组件中需要监听的同时也要取消监听。
这种方式不要乱用,如果在代码中大量的使用事件总线代码,其实是不便于管理和维护的