前面我们是将所有的逻辑放到一个App.vue中:
创建了一个组件App
;将所有的逻辑都放在一个组件中
,那么这个组件就会变成非常的臃肿和难以维护
;对组件进行拆分
,拆分成一个个小的组件
;这些组件组合嵌套在一起
,最终形成我们的应用程序
;观察以下代码,我们可以发现:
- 将所有的代码逻辑全部放到一个组件中,代码是非常的臃肿和难以维护的。
- 并且在真实开发中,我们会有更多的内容和代码逻辑,对于扩展性和可维护性来说都是非常差的。
- 所以,在真实的开发中,我们会对组件进行拆分,拆分成一个个功能的小组件。
<template>
<div id="app">
<div>
<h2>我是标题h2>
<div class="top">topbannerdiv>
<div class="nav">navdiv>
div>
<div>
<h2>contenth2>
<ul>
<li>商品列表1li>
<li>商品列表2li>
<li>商品列表3li>
<li>商品列表4li>
<li>商品列表5li>
ul>
div>
<div>
<h2>Footerh2>
<h2>免责声明h2>
div>
div>
template>
大体上,我们可以按照如下方式拆分:
像上面的代码通过拆分后,我们只需要在App根组件中去编写对应的组件:
<template>
<div id="app">
<app-header>app-header>
<AppContent>AppContent>
<AppFotter>AppFotter>
div>
template>
<script>
import AppHeader from "./components/AppHeader.vue";
import AppContent from "./components/AppContent.vue";
import AppFotter from "./components/AppFooter.vue";
export default {
components:{
AppHeader,
AppContent,
AppFotter,
}
}
script>
<style scoped>
style>
上面的嵌套逻辑有如下关系:
父组件
;父组件
;在开发过程中,我们会经常遇到需要组件之间相互进行通信:
使用了多个Header
,每个地方的Header展示的内容不同
,那么我们就需要使用者传递给Header一些数据
,让其进行展示;请求了Banner数据和ProductList
数据,那么就需要传递给它们
来进行展示;子组件中发生了事件
,需要由父组件来完成某些操作
,那就需要子组件向父组件传递事件
;总之,在一个Vue项目中,组件之间的通信是非常重要的环节,所以接下来我们就具体学习一下组件之间是如何相互之间传递数据的:
(1)父传子:父组件可以通过props向子组件传递属性,然后子组件可以在其模板中使用这些属性。
Props方式一:数组用法
例如我们有如下的一个根组件, 用于展示用户信息, 但是由于子组件中的数据时固定的, 我们展示的两个信息是相同的:
<template>
<ShowInfo name="kobe" age="25" height="1.89">ShowInfo>
template>
<script>
import ShowInfo from "../02_组件通信父传子/ShowInfo.vue";
export default {
components:{
ShowInfo
},
}
script>
<template>
<div class="infos">
<h2>姓名:{{ name }}h2>
<h2>年龄:{{ age }}h2>
<h2>身高:{{ height }}h2>
div>
template>
<script>
export default {
// 接收父组件传递过来的参数
// 1.props数组语法
// -弊端:(1)不能对类型进行验证(2)没有默认值
props:["name","age","height"]
}
script>
props父传子数组方式效果示例:
Props方式二:对象用法
在数组用法中我们只能说明传入的attribute的名称,并不能对其进行任何形式的限制
,
当使用对象语法的时候,我们可以对传入的内容限制更多(重点掌握):
<template>
<ShowInfo name="kobe" :age="25" height="1.89" abc="bca">ShowInfo>
<ShowInfo name="ysl" :age="23" height="1.68">ShowInfo>
<ShowInfo>ShowInfo>
template>
<script>
import ShowInfo from "../02_组件通信父传子/ShowInfo.vue";
export default {
components:{
ShowInfo
},
}
script>
<template>
<div class="infos">
<h2>姓名:{{ name }}h2>
<h2>年龄:{{ age }}h2>
<h2>身高:{{ height }}h2>
div>
template>
<script>
export default {
// 2.props对象语法(掌握)
props: {
name: {
type: String,
// 必传参,加上之后
// required:true,
default: "我是默认name"
},
age: {
type: Number,
default: 0
},
height: {
type: Number,
default: 2
},
friend: {
type: Object,
// 如果是对象类型,这里必须是函数
default: () => ({ name: "james" })
},
hobbies: {
// 数组类型,默认值也写为函数
type: Array,
default() {
return ["唱", "跳", "rap"]
}
}
}
}
script>
props父传子对象方式效果示例:
补充一:
type可以指定多种类型:String,Number,Boolean,Array,Object,Date,Function,Symbol;
补充二:
当type指定类型为对象类型和数组类型时,默认值需要写为一个函数,如下:
Props:{
friend: {
type: Object,
// 如果是对象类型,这里必须是函数
default: () => ({ name: "james" })
},
}
(2)子传父:子组件可以使用$emit向父组件发送事件
,然后父组件可以侦听这些事件并对它们做出响应。
使用场景:
- 当子组件有一些事件发生的时候,比如在组件中发生了点击,父组件需要切换内容;
- 子组件有一些内容想要传递给父组件的时候;
例如:
在子组件中创建一个自定义事件
,然后在触发时使用emit将数据发送
给父组件。<template>
<button @click="handleClick">Click mebutton>
template>
<script>
export default {
methods: {
handleClick() {
this.$emit('button-clicked', 'Hello, World!')
}
}
}
script>
父组件中,我们可以使用@事件名来侦听
这个事件,然后在父组件的方法中处理这些数据:<template>
<my-button @button-clicked="handleButtonClicked">my-button>
template>
<script>
import MyButton from './MyButton.vue'
export default {
components: {
MyButton
},
methods: {
handleButtonClicked(data) {
console.log(data) // 输出 Hello, World!
}
}
}
script>
再写一个计数器案例来练习子传父的操作:
addBtn.vue:
<template>
<div class="add">
<button @click="addClick(1)">+1button>
<button @click="addClick(5)">+5button>
<button @click="addClick(10)">+10button>
div>
template>
<script>
export default {
methods:{
addClick(count){
console.log(count);
this.$emit("add",count)
}
}
}
script>
subBtn.vue:
<template>
<div class="sub">
<button @click="subClick(1)">-1button>
<button @click="subClick(5)">-5button>
<button @click="subClick(10)">-10button>
div>
template>
<script>
export default {
// // 1.emits的数组语法:自定义事件时,在此处注册说明一下,再见他事件时就有提示
// emits:["sub"]:常用,
// 2.emits的对象语法(vue3当中可以对传递的参数就行验证(了解))
emits: {
sub: function (count) {
if (count < 10) {
return true
}
return false
}
},
methods: {
subClick(count) {
console.log(count);
// 让子组件发出去一个自定义事件(事件名称,参数)
this.$emit("sub", count)
}
}
}
script>
App.vue:父组件监听子组件发出的自定义事件, 然后执行对应的操作
<template>
<div id="app">
<h2>计数器:{{ counter }}h2>
<add-btn @add="addCounter">add-btn>
<SubBtn @sub="subCounter">SubBtn>
div>
template>
<script>
import AddBtn from "./components/AddBtn.vue";
import SubBtn from "./components/SubBtn.vue";
export default {
data() {
return {
counter: 0
}
},
components: {
AddBtn,
SubBtn
},
methods: {
addCounter(count) {
this.counter += count;
},
subCounter(count) {
this.counter -= count;
}
}
}
script>
补充:
emits选项是一个数组
,用于在组件中注册自定义事件。声明emits选项后,会为组件自动添加$emit方法
,并在使用时进行类型检查和语法提示,方便使用和开发。export default {
emits: ['test'], // 声明事件名称
methods: {
handleClick() {
this.$emit('test', 'Hello, World!') // 触发test事件
}
}
}
在模板中触发test事件时,代码编辑器会自动提示出test事件名称,方便开发者使用。
- 此章讲解的是, 在学习状态管理Vuex和Pinia之前, 非父子间通信的方案;
- Vuex和Pinia是Vue官方提供的状态管理库,用于管理应用程序中的所有数据
在开发中,我们构建了组件树之后,除了父子组件之间的通信之外,还会有非父子组件之间的通信
。
这里我们主要讲两种方式:
Provide/Inject
;全局事件总线
;(1)Provide和Inject
Provide/Inject用于非父子组件之间共享数据:
深度嵌套
的组件,子组件想要获取父组件的部分
内容;props沿着组件链逐级传递下去
,就会非常的麻烦
;对于这种情况下,我们可以使用 Provide 和 Inject :
无论层级结构有多
深,父组件都可以作为其所有子组件
的依赖提供者;
父组件有一个 provide 选项
来提供数据;
子组件有一个 inject 选项
来开始使用这些数据;
在父组件中,我们可以通过provide提供数据,例如:
export default {
provide: {
message: 'Hello, World!'
}
}
在子组件中,通过inject注入数据:
export default {
inject: ['message'],
created() {
console.log(this.message) // 输出"Hello, World!"
}
}
总结:
provide数据
传出对应的是一个对象
,inject接收
祖先传递的数据对应的是一个数组
provide和inject
在使用时的一个重要问题是数据不是响应式的
,也就是说,当提供的数据发生变化时,子组件不会自动更新。处理响应式数据:
让provide提供的数据变成响应式:
computed函数
;computed是vue3的新特性
,这里以先直接使用,后续会专门讲解;例如,在父组件中,我们提供了一个设置主题的方法和一个存储主题的值:
export default {
data() {
return {
theme: 'light'
}
},
provide() {
return {
setTheme: this.setTheme,
themeState: computed(() => this.theme)
}
},
methods: {
setTheme(value) {
this.theme = value
}
}
}
在上述代码中,我们使用computed将theme属性包装到themeState属性中,当theme属性变化时,themeState会自动更新并通知子组件,这样子组件就可以观察到themeState的变化并更新UI了。
然后在子组件中,我们通过inject注入themeState和setTheme方法,并使用themeState属性来渲染UI。
export default {
inject: ['themeState', 'setTheme'],
computed: {
theme() {
return this.themeState.value
}
}
}
需要注意的是,由于使用了computed
,我们不能直接使用提供的数据,而是需要通过包装后的computed到value属性
来访问它们的值
。
总之,当使用provide和inject进行组件间通信时,为了确保数据的更新能够触发UI的更新,我们需要使用computed属性包装提供的数据。
(2)事件总线
Vue3从实例中移除了 o n 、 on、on、off 和 $once 方法,所以我们如果希望继续使用全局事件总线,要通过第三方的库:
hy-event-store
三方库使用步骤:
npm install hy-event-store
import { HyEventBus } from "hy-event-store";
const eventBus = new HyEventBus()
export default eventBus
在App.vue中监听事件;
在content.vue中触发事件;
content中触发事件:
<template>
<div class="home-content">
<button @click="btnClick">按钮button>
div>
template>
<script>
import eventBus from './utils/event-bus'
export default {
methods: {
btnClick() {
console.log("myEvent事件被监听")
// 发送事件到事件总线上
eventBus.emit("myEvent", "sevgilid", 19, 1.86)
}
},
}
script>
App中监听事件:
<script>
import eventBus from './utils/event-bus'
import Home from './Home.vue'
export default {
components: {
Home
},
created() {
// 监听事件总线上的事件
eventBus.on("myEvent", (name, age, height) => {
console.log(name, age, height)
})
},
}
script>