父组件向子组件通信
方法一:通过 props 属性
- 父组件通过在子组件上定义 props 属性并传递值,将数据传递给子组件。子组件可以通过 props 属性接收父组件传递的数据。
例:创建一个item的组件,在组件中使用props接收值并且给接收的值一些限制
props: {
isActive: {
type: Boolean,//类型为布尔
default: false,//默认值
required: false,//是否为必传
},
}
在使用item组件的地方传值:
<template>
<div>
<Item :isActive ='true'></Item>
</div>
</template>
<script>
import Item from './Item.vue'
export default {
components: { Item },
}
</script>
方法二:通过引用(ref)
- 父组件可以通过 ref 获取子组件的实例,然后调用子组件的方法或直接访问子组件的属性来实现
//父组件
<el-button type="text" @click="addOrUpdateHandle>update</el-button>
<add-or-update ref="addOrUpdate"></add-or-update>
addOrUpdateHandle() {
this.$nextTick(() => {
this.$refs.addOrUpdate.init(this.id)
})
},
//子组件
init(id) {console.log(id);}
子组件向父组件通信
方法一:通过事件传递数据,$emit()方法
子组件通过 $emit
方法触发一个自定义事件,并且可以传递数据给父组件。父组件通过在子组件上监听这个事件来接收数据。
父组件:
<user-info-show @active="test"></user-info-show>
export default {
methods:{
test(e){
console.log("子组件传过来的值 / 123", e);
}
}
}
子组件:
<view @click="handleClick()"></view>
export default {
methods:{
handleClick(){
this.$emit('active',123);
}
}
}
方法二:使用回调函数,props
在父组件中通过 props 将一个函数传递给子组件,在子组件内部调用这个函数并传递数据,实现数据的回调到父组件
在父组件中定义一个函数,并将它作为 props 传递给子组件
//父组件
<template>
<div>
<ChildComponent :callback="handleCallback" />
</div>
</template>
<script>
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
methods: {
handleCallback(data) {
// 处理从子组件传递回来的数据
console.log('Received data:', data);
}
}
}
</script>
在子组件中通过调用回调函数并传递数据,将数据返回给父组件:
//子组件
<template>
<button @click="sendDataToParent">Send Data to Parent</button>
</template>
<script>
export default {
props: ['callback'],
methods: {
sendDataToParent() {
const data = 'Hello from child component';
// 调用父组件传递的回调函数,将数据传递回去
this.callback(data);
}
}
}
</script>
方法三:使用 Vuex 状态管理:
如果应用程序较为复杂,可以使用 Vuex 来管理应用的状态。子组件可以通过提交 mutation 或者触发 action 的方式改变状态,然后父组件可以监听状态的变化来获取数据。
-
在父组件中配置和使用 Vuex:
- 安装 Vuex 并创建一个 Vuex store。
- 在 store 中定义状态(state)和修改状态的方法(mutations)。
-
在子组件中使用 Vuex:
- 在需要使用 Vuex 的子组件中导入 Vuex,并通过
mapState
和mapMutations
等辅助函数,将 Vuex 的状态和方法映射到子组件的计算属性或方法中。
- 在需要使用 Vuex 的子组件中导入 Vuex,并通过
-
在子组件中触发 Vuex 的状态变更:
- 子组件中通过调用映射得到的方法来触发 Vuex 的 mutations,从而修改 Vuex 的状态。
// 父组件 ParentComponent.vue
<template>
<div>
<span>{{ count }}</span>
<ChildComponent />
</div>
</template>
<script>
import { mapState } from 'vuex';
import ChildComponent from './ChildComponent.vue';
export default {
components: {
ChildComponent
},
computed: {
...mapState(['count']) // 将 count 映射到父组件的计算属性中
}
}
</script>
// 子组件 ChildComponent.vue
<template>
<div>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { mapMutations } from 'vuex';
export default {
methods: {
...mapMutations(['increment']) // 将 increment 映射到子组件的方法中
}
}
</script>
案例:列表菜单
单个使用 循环使用
Item.vue
<template>
<div :class="{ active: isActive }" @click="handleClick">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
isActive: {
default: false, //默认值
type: Boolean, //类型
},
},
methods: {
handleClick() {
this.$emit('active')//使用 $emit 方法触发active自定义事件,并通过自定义事件来实现组件之间的通信
},
},
}
</script>
<style scoped>
.active {
background: rgb(204, 202, 202);
}
</style>
TitleMenu.vue
<template>
<div class="menu_box">
<Item :isActive="isActive" @active="$emit('active')">//@active="$emit('active')"父组件接收到名为 active 的事件时,立即触发一个同名的自定义事件 active 并将其向上传播抛给父元素
<div class="title_menu">
<div>
<slot name="title"></slot>//具名插槽,通过name指定名称来区分不同的插槽
</div>
<div>
<slot name="icon"></slot>
</div>
</div>
</Item>
</div>
</template>
<script>
import Item from './Item.vue'
export default {
props: {
isActive: {
default: false, //默认值
type: Boolean, //类型
},
},
components: {
Item,
},
}
</script>
<style scoped>
.menu_box {
transition: 0.5s;
}
.menu_box:hover {
background: rgb(219, 147, 147);
}
.title_menu {
padding: 5px;
display: flex;
align-items: center;
justify-content: space-between;
}
</style>
单个使用:
<template>
<div style="width: 250px;">
<TitleMenu :isActive="select" @active="select = true">
<template v-slot:title>发现频道</template>
<template v-slot:icon>></template>
</TitleMenu>
</div>
</template>
<script>
import TitleMenu from '../../components/common/TitleMenu.vue'
export default {
components: { TitleMenu },
data() {
return {
select: false,
}
},
}
</script>
循环使用:通过接口渲染数据,实现只有一个 select
状态为 true
的功能
<template>
<div style="width: 250px;">
<TitleMenu
:isActive="item.select"
@active="updateSelect(index)"
v-for="(item, index) in titleData"
:key="index"
>
<template v-slot:title>{{ item.uname }}</template>
<template v-slot:icon>></template>
</TitleMenu>
</div>
</template>
<script>
import TitleMenu from '../../components/common/TitleMenu.vue'
export default {
components: { TitleMenu },
data() {
return {
select: false,
titleData: [],
}
},
methods: {
updateSelect(index) {
this.titleData.forEach((item, i) => {
//this.$set(item, 'newProperty', 'value');给数组的每一项添加新属性
// i === index当前循环的索引值 i 是否等于点击的菜单项的索引 index,结果为 true 或 false
this.$set(item, 'select', i === index);
})
},
},
}
</script>
案例:动态改变数据列数
columus="2" columus="3"
<template>
<div class="channel_list">
<div
v-for="item in channelList"
:key="item.id"
class="item"
:style="{ width: `${100 / columus}%` }"
>...</div>
</div>
</template>
<script>
export default {
props: {
columus: {
type: Number,
default: 2,//默认2列
},
},
data() {
return {
channelList: [],
}
},
}
</script>
<style scoped>
.channel_list {
overflow: hidden;
}
.item {
float: left;
}
</style>
<ChanneList :columus='2'></ChanneList>
案例:封装搜索框
封装一个搜索框,宽度和高度由父元素控制
封装图标icon:
<template>
<div>
<i :class="iconname"></i>
</div>
</template>
<script>
export default {
props: {
iconname: {
type: String,
default: 'el-icon-edit',
required: true,
},
},
}
</script>
封装Search:
<template>
<form class="search_h" @submit.prevent="handleSearch">
<input type="text" v-model="value" :placeholder="placeholder"></input>
<div @click="handleSearch">
<Icon class="icon" :iconname="'el-icon-search'"></Icon>
</div>
</form>
</template>
<script>
import Icon from './Icon.vue'
export default {
components: {
Icon,
},
props:{
placeholder:{
type:String,
default:'请输入搜索内容'
}
},
data() {
return {
value:''
}
},
methods: {
handleSearch() {
if(this.value){//搜索框里有值的时候才触发事件
this.$emit('search',this.value)
}
},
}
}
</script>
<style scoped>
.search_h {
border: 1px solid #cec9c9;
width: 100%;
height: 100%;
box-sizing: border-box;
border-right: 2px;
position: relative;
}
.search_h input{
border: none;
outline: none;
width: 100%;
height: 100%;
padding-left: 10px;
padding-right: 40px;
box-sizing: border-box;
}
.icon{
background: #aaa5a5;
color: white;
text-align: center;
width: 40px;
height: 100%;
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>
使用:
<div style="width: 100%; height: 30px;">
<Search :placeholder="'请输入'" @search="handleSearch"></Search>
</div>
import Search from '../../components/common/search.vue'
components: { Search },
handleSearch(e) {
console.log(e, '搜索框里的内容')
},
案例:封装一个侧边栏
//icon.vue
<template>
<div>
<i :class="iconname"></i>
</div>
</template>
<script>
export default {
props: {
iconname: {
type: String,
default: 'el-icon-edit',
required: true,
},
},
}
</script>
//item.vue
<template>
<div :class="{ active: isActive }" @click="handleClick">
<slot></slot>
</div>
</template>
<script>
export default {
props: {
isActive: {
default: false, //默认值
type: Boolean, //类型
},
},
methods: {
handleClick() {
this.$emit('active')
},
},
}
</script>
<style scoped>
.active {
background: rgb(204, 202, 202);
}
</style>
//search.vue
<template>
<form class="search_h" @submit.prevent="handleSearch">
<input type="text" v-model="value" :placeholder="placeholder"></input>
<div @click="handleSearch">
<Icon class="icon" :iconname="'el-icon-search'"></Icon>
</div>
</form>
</template>
<script>
import Icon from './Icon.vue'
export default {
components: {
Icon,
},
props:{
placeholder:{
type:String,
default:'请输入搜索内容'
}
},
data() {
return {
value:''
}
},
methods: {
handleSearch() {
if(this.value){//搜索框里有值的时候才触发事件
this.$emit('search',this.value)
}
},
}
}
</script>
<style scoped>
.search_h {
border: 1px solid #cec9c9;
width: 100%;
height: 100%;
box-sizing: border-box;
border-right: 2px;
position: relative;
}
.search_h input{
border: none;
outline: none;
width: 100%;
height: 100%;
padding-left: 10px;
padding-right: 40px;
box-sizing: border-box;
}
.icon{
background: #aaa5a5;
color: white;
text-align: center;
width: 40px;
height: 100%;
position: absolute;
top: 0;
right: 0;
display: flex;
align-items: center;
justify-content: center;
cursor: pointer;
}
</style>
//TitleMenu.vue
<template>
<div class="menu_box">
<Item :isActive="isActive" @active="$emit('active')">
<div class="title_menu">
<div class="title">
<slot name="title"></slot>
</div>
<div class="icon">
<slot name="icon"></slot>
</div>
</div>
</Item>
</div>
</template>
<script>
import Item from './Item.vue'
export default {
props: {
isActive: {
default: false, //默认值
type: Boolean, //类型
},
},
components: {
Item,
},
}
</script>
<style scoped>
.menu_box {
transition: 0.5s;
}
.menu_box:hover {
background: rgb(219, 147, 147);
}
.title_menu {
padding: 5px;
display: flex;
align-items: center;
justify-content: space-between;
}
.title{
font-weight: bolder;
}
.icon{
color: #6d757a;
}
</style>
//channel.vue
<template>
<div class="menu_box" v-if="data">
<Item :isActive="isActive" @active="$emit('active')">
<div class="title_menu">
<span style="font-size: 14px;">
{{ data.name }}
</span>
<span style="color: #6d757a; font-size: 12px;">
{{ data.count }}
</span>
</div>
</Item>
</div>
</template>
<script>
import Item from './Item.vue'
export default {
props: {
isActive: {
default: false, //默认值
type: Boolean, //类型
},
data: {
type: Object,
default: function () {
return {}
},
},
},
components: {
Item,
},
}
</script>
<style scoped>
.menu_box {
transition: 0.5s;
}
.menu_box:hover {
background: rgb(219, 217, 217);
}
.title_menu {
padding: 5px;
}
</style>
//channelist.vue
<template>
<div>
<div class="channel_list" :style="{ height: `${height}px` }">
<div
v-for="item in channelList"
:key="item.id"
class="item"
:style="{ width: `${100 / columus}%` }"
>
<Channe
@active="$emit('active', item.id)"
:isActive="item.id === activeId"
:data="item"
></Channe>
</div>
</div>
<div class="collapse" @click="isExpand = !isExpand">
<span>{{ isExpand == true ? '折叠' : '展开' }}</span>
<Icon
:iconname="isExpand == true ? 'el-icon-arrow-down' : 'el-icon-arrow-up'"
></Icon>
</div>
</div>
</template>
<script>
import Channe from '../../components/common/channel.vue'
import Icon from '../../components/common/Icon.vue'
export default {
props: {
activeId: {
type: Number,
default: 0,
},
columus: {
type: Number,
default: 2,
},
},
components: {
Channe,
Icon,
},
data() {
return {
channelList: [
{ id: 1, name: '动漫', count: '256' },
{ id: 2, name: '电视剧', count: '56' },
{ id: 3, name: '热门', count: '26' },
...
],
isExpand: true, //是否是展开状态
}
},
computed: {
height() {
var rows = 3
if (this.isExpand) {
rows = Math.ceil(this.channelList.length / this.columus)
}
return rows * 32
},
},
}
</script>
<style scoped>
.channel_list {
overflow: hidden;
transition: 1s;
}
.item {
float: left;
}
.collapse {
border: 1px solid #d3d0d0;
display: flex;
justify-content: center;
align-items: center;
clear: both;
color: rgb(105, 102, 99);
cursor: pointer;
width: 100%;
height: 40px;
line-height: 40px;
text-align: center;
}
</style>
//Aside.vue
<template>
<div>
<div>
<div style="width: 100%; height: 30px;">
<Search :placeholder="'请输入'" @search="handleSearch"></Search>
</div>
<TitleMenu :isActive="activeId === 3" @active="activeId = 3">
<template v-slot:title>发现频道</template>
<template v-slot:icon>></template>
</TitleMenu>
</div>
<ChanneList
:columus="2"
@active="activeId = $event"
:activeId="activeId"
></ChanneList>
</div>
</template>
<script>
import TitleMenu from './TitleMenu.vue'
import ChanneList from './channelist.vue'
import Search from './search.vue'
export default {
components: { TitleMenu, ChanneList, Search },
data() {
return {
select: false,
titleData: [],
activeId: 1,
}
},
methods: {
handleSearch(e) {
console.log(e, '搜索')
},
updateSelect(index) {
this.titleData.forEach((item, i) => {
this.$set(item, 'select', i === index)
})
},
},
}
</script>