一、父传子
1、props
- 用法:
(1)父组件用 props绑定数据,表示为 v-bind:props="数据" (v-bind:简写为 : ,props可以任意命名)
(2)子组件用 defineProps(['props',....])接收
- 注意:
(1)v-bind:c="数据" 表示父组件给数据绑定了一个名为c的prop属性。这样当父组件的数据发生改变,子组件也能接收到,使双方数据同步;
(2)父传子prop是非函数;
(3)props绑定的数据是只读属性,不能进行修改
父组件Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4>汽车:{{ car }}</h4>
<Child v-bind:c="car"/>
</div>
</template>
<script setup lang="ts" name="Father">
// @ts-nocheck
import Child from './Child.vue'
import {ref} from 'vue'
// 数据
let car = ref('奔驰')
</script>
子组件 Child.vue
<template>
<div class="child">
<h3>子组件</h3>
<h4>父给的车:{{ c }}</h4>
</div>
</template>
<script setup lang="ts" name="Child">
// @ts-nocheck
import {ref} from 'vue'
// 声明接收props
defineProps(['c'])
</script>
2、v-model
用法:
(1)父组件 使用v-model给数据绑定
(2)子组件 利用了props和自定义事件的组合使用
父组件
<Child1 v-model:pageNo="pageNo" v-model:pageSize="pageSize"></Child1>
//父亲的数据
let pageNo = ref(1)
let pageSize = ref(3)
子组件
<template>
<div class="child2">
<h1>同时绑定多个v-model</h1>
<button @click="handler">pageNo{{ pageNo }}</button>
<button @click="$emit('update:pageSize', pageSize + 4)">
pageSize{{ pageSize }}
</button>
</div>
</template>
<script setup lang="ts">
let props = defineProps(["pageNo", "pageSize"]);
let $emit = defineEmits(["update:pageNo", "update:pageSize"]);
//第一个按钮的事件回调
const handler = () => {
$emit("update:pageNo", props.pageNo + 3);
};
</script>
3、$refs
用法:
ref
(1)父组件 给所有子组件用ref打标识,定义点击事件以及回调。
(2)子组件 defineExpose暴露自己的数据,父亲可以拿到
$refs
父亲想修改所有儿子的数据。
(1)父组件定义一个修改全部儿子的方法传入参数$refs。$refs可以获取所有儿子实例
(2)子组件都使用defineExpose向外暴露自己的数据
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>房产:{{ house }}</h4>
<button @click="changeToy">修改Child1的玩具</button>
<button @click="changeComputer">修改Child2的电脑</button>
<button @click="getAllChild($refs)">让所有孩子的书变多</button>
<Child1 ref="c1"/>
<Child2 ref="c2"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref,reactive } from "vue";
let c1 = ref()
let c2 = ref()
// 注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是在obj这个响应式对象中的
/* let obj = reactive({
a:1,
b:2,
c:ref(3)
})
let x = ref(4)
console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x) */
// 数据
let house = ref(4)
// 方法
function changeToy(){
c1.value.toy = '小猪佩奇'
}
function changeComputer(){
c2.value.computer = '华为'
}
function getAllChild(refs:{[key:string]:any}){
console.log(refs)
for (let key in refs){
refs[key].book += 3
}
}
// 向外部提供数据
defineExpose({house})
</script>
子组件1:
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<h4>书籍:{{ book }} 本</h4>
<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
</div>
</template>
<script setup lang="ts" name="Child1">
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
let book = ref(3)
// 方法
function minusHouse(parent:any){
parent.house -= 1
}
// 把数据交给外部
defineExpose({toy,book})
</script>
子组件2:
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑:{{ computer }}</h4>
<h4>书籍:{{ book }} 本</h4>
</div>
</template>
<script setup lang="ts" name="Child2">
import { ref } from "vue";
// 数据
let computer = ref('联想')
let book = ref(6)
// 把数据交给外部
defineExpose({computer,book})
</script>
4、默认插槽
用法:
(1)父组件 使用category子组件的起始终止标签,里面夹带要展示的内容
(2)子组件 使用slot默认插槽,父组件的每一个category标签中间的内容都会插入到slot里面
这样就能把父亲里面的数据显示到子组件里面。
父组件
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Category title="热门游戏列表">
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</Category>
<Category title="今日美食城市">
<img :src="imgUrl" alt="">
</Category>
<Category title="今日影视推荐">
<video :src="videoUrl" controls></video>
</Category>
</div>
</div>
</template>
<script setup lang="ts" name="Father">
import Category from './Category.vue'
import { ref,reactive } from "vue";
let games = reactive([
{id:'asgytdfats01',name:'英雄联盟'},
{id:'asgytdfats02',name:'王者农药'},
{id:'asgytdfats03',name:'红色警戒'},
{id:'asgytdfats04',name:'斗罗大陆'}
])
let imgUrl = ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
let videoUrl = ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')
</script>
子组件
<template>
<div class="category">
<h2>{{title}}</h2>
<slot>默认内容</slot>
</div>
</template>
<script setup lang="ts" name="Category">
defineProps(['title'])
</script>
5、具名插槽
用法:
(1)父组件 用template v-slot:s1或者#s2来命名
(2)子组件 用slot name=xxx来决定存放位置
这样父组件里面的数据就能决定放在子组件的哪个位置了,不受顺序影响
父组件中:
<Category title="今日热门游戏">
<template v-slot:s1>
<ul>
<li v-for="g in games" :key="g.id">{{ g.name }}</li>
</ul>
</template>
<template #s2>
<a href="">更多</a>
</template>
</Category>
子组件中:
<template>
<div class="item">
<h3>{{ title }}</h3>
<slot name="s1"></slot>
<slot name="s2"></slot>
</div>
</template>
二、子传父
1、props
- 用法:
(1)先在父组件内部定义一个关于获取数据的方法getToy,为这个方法getToy绑定prop属性为函数sendToy。表示为 :sendToy="getToy"
(2)子组件definprops(['prop',....])接收,并绑定点击事件@click="sendToy(toy)"把要传的数据发送过去。
- 注意:
(1)子传父prop是函数
(2)props绑定的数据是只读属性,不能进行修改
父组件Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
<Child :sendToy="getToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
// @ts-nocheck
import Child from './Child.vue'
import {ref} from 'vue'
// 数据
let toy = ref('')
// 方法
function getToy(value:string){
toy.value = value
}
</script>
子组件Child.vue
<template>
<div class="child">
<h3>子组件</h3>
<button @click="sendToy(toy)">把玩具给父亲</button>
</div>
</template>
<script setup lang="ts" name="Child">
// @ts-nocheck
import {ref} from 'vue'
// 数据
let toy = ref('奥特曼')
// 声明接收props
defineProps(['sendToy'])
</script>
2、emit自定义事件通信
用法:
(1) 先在父组件内部的子组件绑定自定义事件@xxx,即为 @send-toy="saveToy",再定义自定义事件的回调saveToy
(2)子组件声明事件,用definEmits(['send-toy'])
(3)子组件绑定事件传递参数,@click="emit('send-toy',toy)"
Father.vue
<template>
<div class="father">
<h3>父组件</h3>
<h4 v-show="toy">子给的玩具:{{ toy }}</h4>
<!-- 给子组件Child绑定事件 -->
<Child @send-toy="saveToy"/>
</div>
</template>
<script setup lang="ts" name="Father">
// @ts-nocheck
import Child from './Child.vue'
import { ref } from "vue";
// 数据
let toy = ref('')
// 用于保存传递过来的玩具
function saveToy(value:string){
console.log('saveToy',value)
toy.value = value
}
</script>
Child.vue
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emit('send-toy',toy)">测试</button>
</div>
</template>
<script setup lang="ts" name="Child">
// @ts-nocheck
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
// 声明事件
const emit = defineEmits(['send-toy'])
</script>
3、v-model
4、$parent
用法:
(1)父组件,用definExpose向外暴露自己的数据
(2)子组件,通过$parent获取父组件实例,定义点击事件传入$parent 修改父组件的数据
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>房产:{{ house }}</h4>
<button @click="changeToy">修改Child1的玩具</button>
<button @click="changeComputer">修改Child2的电脑</button>
<button @click="getAllChild($refs)">让所有孩子的书变多</button>
<Child1 ref="c1"/>
<Child2 ref="c2"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
import { ref,reactive } from "vue";
let c1 = ref()
let c2 = ref()
// 注意点:当访问obj.c的时候,底层会自动读取value属性,因为c是在obj这个响应式对象中的
/* let obj = reactive({
a:1,
b:2,
c:ref(3)
})
let x = ref(4)
console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x) */
// 数据
let house = ref(4)
// 方法
function changeToy(){
c1.value.toy = '小猪佩奇'
}
function changeComputer(){
c2.value.computer = '华为'
}
function getAllChild(refs:{[key:string]:any}){
console.log(refs)
for (let key in refs){
refs[key].book += 3
}
}
// 向外部提供数据
defineExpose({house})
</script>
子组件1:
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<h4>书籍:{{ book }} 本</h4>
<button @click="minusHouse($parent)">干掉父亲的一套房产</button>
</div>
</template>
<script setup lang="ts" name="Child1">
import { ref } from "vue";
// 数据
let toy = ref('奥特曼')
let book = ref(3)
// 方法
function minusHouse(parent:any){
parent.house -= 1
}
// 把数据交给外部
defineExpose({toy,book})
</script>
5、作用域插槽
比如你要在父组件显示子组件的不同形式(有序,无序,标题),但是当你在父组件遍历子组件的数据时候,你是拿不到的。这时候就用到了作用域插槽。数据在子组件,数据的展示解构由父组件决定。
用法:
(1)父组件 使用template v-slot="params" params就是子组件传递过来的所有对象
(2)子组件 用props绑定数据,传给了内置组件slot,slot又把数据传给了他的使用者,父组件。
父组件
<template>
<div class="father">
<h3>父组件</h3>
<div class="content">
<Game>
<template v-slot="params">
<ul>
<li v-for="y in params.youxi" :key="y.id">
{{ y.name }}
</li>
</ul>
</template>
</Game>
<Game>
<template v-slot="params">
<ol>
<li v-for="item in params.youxi" :key="item.id">
{{ item.name }}
</li>
</ol>
</template>
</Game>
<Game>
<template #default="{youxi}">
<h3 v-for="g in youxi" :key="g.id">{{ g.name }}</h3>
</template>
</Game>
</div>
</div>
</template>
<script setup lang="ts" name="Father">
import Game from './Game.vue'
</script>
子组件
<template>
<div class="game">
<h2>游戏列表</h2>
<slot :youxi="games" x="哈哈" y="你好"></slot>
</div>
</template>
<script setup lang="ts" name="Game">
import {reactive} from 'vue'
let games = reactive([
{id:'asgytdfats01',name:'英雄联盟'},
{id:'asgytdfats02',name:'王者农药'},
{id:'asgytdfats03',name:'红色警戒'},
{id:'asgytdfats04',name:'斗罗大陆'}
])
</script>
三、祖传孙
1、$attrs
用法:
(1)父组件 使用v-bind给数据绑定props
(2)子组件 不接收,使用v-bind="$attrs"存给孙组件
(3)孙组件 使用defineprops接收
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>a:{{a}}</h4>
<h4>b:{{b}}</h4>
<h4>c:{{c}}</h4>
<h4>d:{{d}}</h4>
<Child :a="a" :b="b" :c="c" :d="d" />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref} from 'vue'
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
</script>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孙组件:
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a','b','c','d'])
</script>
2、provide inject
用法:
(1)父组件,先从vue中import引入provide ,而后使用provide向后代提供数据
provide('名字',值)
(2)后代组件,先import引入inject,而后使用inject接收数据,inject('名字','默认值')
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>银子:{{ money }}万元</h4>
<h4>车子:一辆{{car.brand}}车,价值{{car.price}}万元</h4>
<Child/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref,reactive,provide} from 'vue'
let money = ref(100)
let car = reactive({
brand:'奔驰',
price:100
})
function updateMoney(value:number){
money.value -= value
}
// 向后代提供数据
provide('moneyContext',{money,updateMoney})
provide('car',car)
孙组件:
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
<h4>银子:{{ money }}</h4>
<h4>车子:一辆{{car.brand}}车,价值{{car.price}}万元</h4>
<button @click="updateMoney(6)">花爷爷的钱</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
import { inject } from "vue";
let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(param:number)=>{}})
let car = inject('car',{brand:'未知',price:0})
</script>
四、孙传祖
1、$attrs
(1)父组件 绑定方法
(2)子组件 不接收使用$attrs传给孙组件
(3)孙组件 使用defineprops接收,并绑定点击事件
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>a:{{a}}</h4>
<h4>b:{{b}}</h4>
<h4>c:{{c}}</h4>
<h4>d:{{d}}</h4>
<Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref} from 'vue'
let a = ref(1)
let b = ref(2)
let c = ref(3)
let d = ref(4)
function updateA(value:number){
a.value += value
}
</script>
子组件:
<template>
<div class="child">
<h3>子组件</h3>
<GrandChild v-bind="$attrs"/>
</div>
</template>
<script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue'
</script>
孙组件:
<template>
<div class="grand-child">
<h3>孙组件</h3>
<h4>a:{{ a }}</h4>
<h4>b:{{ b }}</h4>
<h4>c:{{ c }}</h4>
<h4>d:{{ d }}</h4>
<h4>x:{{ x }}</h4>
<h4>y:{{ y }}</h4>
<button @click="updateA(6)">点我将爷爷那的a更新</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
defineProps(['a','b','c','d','x','y','updateA'])
</script>
2、provide inject
用法:
(1) 父组件,里面定义一个方法,在provide里面传的是money的值和这个方法给孙组件
(2)孙组件,可以把传过来的值和方法 利用解构赋值和inject接收,然后再添加一个按钮绑定这个方法,并传数据。就实现了孙传祖。
父组件:
<template>
<div class="father">
<h3>父组件</h3>
<h4>银子:{{ money }}万元</h4>
<h4>车子:一辆{{car.brand}}车,价值{{car.price}}万元</h4>
<Child/>
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import {ref,reactive,provide} from 'vue'
let money = ref(100)
let car = reactive({
brand:'奔驰',
price:100
})
function updateMoney(value:number){
money.value -= value
}
// 向后代提供数据
provide('moneyContext',{money,updateMoney})
provide('car',car)
孙组件:
<template>
<div class="grand-child">
<h3>我是孙组件</h3>
<h4>银子:{{ money }}</h4>
<h4>车子:一辆{{car.brand}}车,价值{{car.price}}万元</h4>
<button @click="updateMoney(6)">花爷爷的钱</button>
</div>
</template>
<script setup lang="ts" name="GrandChild">
import { inject } from "vue";
let {money,updateMoney} = inject('moneyContext',{money:0,updateMoney:(param:number)=>{}})
let car = inject('car',{brand:'未知',price:0})
</script>
五、兄弟、任意组件
1、mitt任意组件通信
用法:
(1)提供数据的用emitter.emit触发事件。
@click="emitter.emit('send-toy',toy)">
(2)接收数据的用emitter.on绑定事件。
emitter.on('send-toy',(value:any)=>{
toy.value = value
})
Child1.vue
<template>
<div class="child1">
<h3>子组件1</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emitter.emit('send-toy',toy)">玩具给弟弟</button>
</div>
</template>
<script setup lang="ts" name="Child1">
// @ts-nocheck
import {ref} from 'vue'
import emitter from '@/utils/emitter';
// 数据
let toy = ref('奥特曼')
</script>
Child2.vue
<template>
<div class="child2">
<h3>子组件2</h3>
<h4>电脑:{{ computer }}</h4>
<h4>哥哥给的玩具:{{ toy }}</h4>
</div>
</template>
<script setup lang="ts" name="Child2">
// @ts-nocheck
import {ref,onUnmounted} from 'vue'
import emitter from '@/utils/emitter';
// 数据
let computer = ref('联想')
let toy = ref('')
// 给emitter绑定send-toy事件
emitter.on('send-toy',(value:any)=>{
toy.value = value
})
// 在组件卸载时解绑send-toy事件
onUnmounted(()=>{
emitter.off('send-toy')
})
</script>
2、pinia
vue3组件常用的通信方式 Pinia_pinia实现组件的通信-CSDN博客
// 在 store.js 中定义 Pinia store
import { defineStore } from 'pinia';
export const useCounterStore = defineStore({
id: 'counter',
state: () => ({
count: 0,
}),
actions: {
increment() {
this.count++;
},
decrement() {
this.count--;
},
},
});
// 在组件中使用 store
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
</template>
<script>
import { useCounterStore } from './store';
export default {
setup() {
const counterStore = useCounterStore();
return {
count: counterStore.count,
increment: counterStore.increment,
decrement: counterStore.decrement,
};
},
};
</script>