1.Props
父传子:直接传递需要获取的属性
子传父:需要借助函数,也就是方法,通过传递函数,子接着入参给函数,父调用函数即可获取到参数。
父:
<template>
<div class="father">
<h3>父组件</h3>
<h4>汽车:{{ car }}</h4>
<h4 v-show="toy">儿子给的玩具:{{ toy }}</h4>
<Child :car="car" :sendToy="getToy" />
</div>
</template>
<script setup lang="ts" name="Father">
import Child from './Child.vue'
import { ref } from 'vue';
let car = ref('奔驰')
let toy = ref('')
function getToy(value: string) {
console.log(value)
toy.value = value
}
</script>
子:
<template>
<div class="child">
<h3>子组件</h3>
<h4>玩具:{{ toy }}</h4>
<h4>父亲给的汽车:{{ car }}</h4>
<button @click="sendToy(toy)">给父亲玩具</button>
</div>
</template>
<script setup lang="ts" name="Child">
import { ref } from 'vue';
let toy = ref('奥特曼')
defineProps(['car', 'sendToy'])
</script>
2.自定义事件
使用@,声明一个自定义事件,并绑定一个函数,传递给子子组件通过接收该自定义事件,
<!-- 自定义事件 -->
<Child @send-toy="saveToy" />
子接收自定义时间,并绑定点击事件,通过入参,实现数据的传递
let emit = defineEmits(['send-toy'])
<button @click="emit('send-toy',toy)">传递玩具给父亲</button>
3.mitt(绑定-订阅接收)任意组件消息传递(类似于RabbitMQ)
import mitt from 'mitt'
import { ref } from 'vue'
let emitter = mitt()
let num = ref(0)
// 绑定事件
emitter.on('add', () => {
console.log('add被调用了', num)
})
// 每隔1秒执行事件
setInterval(() => {
emitter.emit('add')
}, 1000)
setTimeout(() => {
// 解绑事件
emitter.off('add')
}, 5000)
export default emitter
接收数据,需要绑定事件,也就是订阅。避免内存溢出,组件卸载之后,解绑事件
let food = ref('')
emitter.on('send-food', (value: string) => {
food.value = value
})
onUnmounted(() => {
emitter.off('send-food')
})
发送数据,需要执行事件
<button @click="emitter.emit('send-food', food)">分享食物给弟弟</button>
4.v-model,一般用于UI库的底层实现
例如el input 输入框,底层编写了输入框组件,并且通过v-model实现数据的双向绑定
5.$attrs
父组件向子组件传递数据,attrs存储的是被defineProps接收的值,可以通过$attrs获取
<Child :car="car" :sendToy="getToy" @send-toy="saveToy" :a="a" :b="b" :updateA="updateA"/>
defineProps(['car', 'sendToy'])
<h4>attrs:a:{{ $attrs.a }}</h4>
<h4>attrs:b:{{ $attrs.b }}</h4>
<button @click="$attrs.updateA(6)">修改a</button>
6.$refs,$parent
都需要集合defineExpose,将需要传递的数暴露出去
$refs :子传父
<button @click="addBook($refs)">给孩子买书</button>
<Child ref="c1" />
let c1 = ref()
function updateA(value: number) {
a.value += value
}
defineExpose({book})
$parent:父传子
defineExpose({ house })
<button @click="minusHouse($parent)">得到父亲的一套房子</button>
function minusHouse(parent:any){
console.log(parent)
parent.house -=1
}
7.provide(提供数据)、inject(接收数据)
let money = ref(100)
provide('moneyContext',{money,updateMoney})
<h4>父亲的钱:{{ money }}</h4>
<button @click="updateMoney(6)">花父亲的钱</button>
let { money, updateMoney } = inject('moneyContext', { money: 55, updateMoney: (value: number) => { } })
8.插槽slot
8.1 默认插槽
引入子组件,组间标签内编写内容
在子组件,使用<slot>j进行占位
<template>
<div class="father">
<h2>父组件</h2>
<div class="content">
<Child title="今日游戏列表">
<ul>
<li v-for=" (item, index) in games" :key="index">{{ item }}</li>
</ul>
</Child>
<Child title="今日美食推荐">
<img :src="imgUrl">
</Child>
<Child title="今日影视推荐">
<video :src="videoUrl" controls />
</Child>
</div>
</div>
</template>
<script>
export default {
name: 'Father'
}
</script>
<script setup>
import Child from './Child.vue';
import { ref, reactive } from 'vue';
let games = reactive([
'王者荣耀',
'绝地求生',
'天天酷跑'
])
let imgUrl = ref('http://47.113.201.132:9001/api/v1/download-shared-object/aHR0cDovLzEyNy4wLjAuMTo5MDAwL215bGlmZS8yMDI0LzA4LzE2L2NkNjY3YWY2YzAxMzRiNmZhZGZhMTMwZjk4NGQ0MGUxLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPVNVTEdOWTY3RVpFMFoyVVZNVTFUJTJGMjAyNDA4MzAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODMwVDAxNTEzNVomWC1BbXotRXhwaXJlcz00MzE5NiZYLUFtei1TZWN1cml0eS1Ub2tlbj1leUpoYkdjaU9pSklVelV4TWlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaFkyTmxjM05MWlhraU9pSlRWVXhIVGxrMk4wVmFSVEJhTWxWV1RWVXhWQ0lzSW1WNGNDSTZNVGN5TlRBeU5UZ3lOQ3dpY0dGeVpXNTBJam9pY205dmRDSjkuOElYdU9CR2w3V2p5QWR5LUpoSVRMQml5dmJTRmhHM3lLNmZpUWE3RS1aaHQzV3o3TkdsMzdGUjlTal90aXdLNk4ySmxtcUhRNmtSQmxtODVzajNUa2cmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnZlcnNpb25JZD1udWxsJlgtQW16LVNpZ25hdHVyZT0xNjE5NmViYzcwZTM2MDkxNDQ4Y2ZmMmMzYTc3ODY4NzZmZTllNDhmYTZkMGQ5Y2JkMmU4NzE2NmM3MDQ4YWQw')
let videoUrl = ref('http://vjs.zencdn.net/v/oceans.mp4')
</script>
<style scoped>
.father {
background-color: antiquewhite;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 10px;
}
.content {
display: flex;
justify-content: space-evenly;
}
img,
video {
width: 90%;
}
</style>
<template>
<div class="child">
<h2>{{ title }}</h2>
<slot></slot>
</div>
</template>
<script>
export default {
name: 'Child'
}
</script>
<script setup>
defineProps(['title'])
</script>
<style scoped>
.child {
background-color: aquamarine;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
width: 200px;
height: 300px;
}
h2 {
background-color: yellow;
text-align: center;
}
</style>
8.2 具名插槽:使用多个插槽,填充不同的内容
数据在父组件,结构也在父组件,填充的位置由子组件定义。
<template>
<div class="father">
<h2>父组件</h2>
<div class="content">
<Child>
<template v-slot:s1>
<h2>今日游戏列表</h2>
</template>
<template v-slot:s2>
<ul>
<li v-for=" (item, index) in games" :key="index">{{ item }}</li>
</ul>
</template>
</Child>
<Child>
<template v-slot:s1>
<h2>今日美食推荐</h2>
</template>
<template v-slot:s2>
<img :src="imgUrl">
</template>
</Child>
<Child>
<template #s1>
<h2>今日影视推荐</h2>
</template>
<template #s2>
<video :src="videoUrl" controls />
</template>
</Child>
</div>
</div>
</template>
<script>
export default {
name: 'Father'
}
</script>
<script setup>
import Child from './Child.vue';
import { ref, reactive } from 'vue';
let games = reactive([
'王者荣耀',
'绝地求生',
'天天酷跑'
])
let imgUrl = ref('http://47.113.201.132:9001/api/v1/download-shared-object/aHR0cDovLzEyNy4wLjAuMTo5MDAwL215bGlmZS8yMDI0LzA4LzE2L2NkNjY3YWY2YzAxMzRiNmZhZGZhMTMwZjk4NGQ0MGUxLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPVNVTEdOWTY3RVpFMFoyVVZNVTFUJTJGMjAyNDA4MzAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODMwVDAxNTEzNVomWC1BbXotRXhwaXJlcz00MzE5NiZYLUFtei1TZWN1cml0eS1Ub2tlbj1leUpoYkdjaU9pSklVelV4TWlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaFkyTmxjM05MWlhraU9pSlRWVXhIVGxrMk4wVmFSVEJhTWxWV1RWVXhWQ0lzSW1WNGNDSTZNVGN5TlRBeU5UZ3lOQ3dpY0dGeVpXNTBJam9pY205dmRDSjkuOElYdU9CR2w3V2p5QWR5LUpoSVRMQml5dmJTRmhHM3lLNmZpUWE3RS1aaHQzV3o3TkdsMzdGUjlTal90aXdLNk4ySmxtcUhRNmtSQmxtODVzajNUa2cmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnZlcnNpb25JZD1udWxsJlgtQW16LVNpZ25hdHVyZT0xNjE5NmViYzcwZTM2MDkxNDQ4Y2ZmMmMzYTc3ODY4NzZmZTllNDhmYTZkMGQ5Y2JkMmU4NzE2NmM3MDQ4YWQw')
let videoUrl = ref('http://vjs.zencdn.net/v/oceans.mp4')
</script>
<style scoped>
h2 {
background-color: yellow;
text-align: center;
}
.father {
background-color: antiquewhite;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 10px;
}
.content {
display: flex;
justify-content: space-evenly;
}
img,
video {
width: 90%;
}
</style>
<template>
<div class="child">
<slot name="s1">默认内容1</slot>
<slot name="s2">默认内容2</slot>
</div>
</template>
<script>
export default {
name: 'Child'
}
</script>
<script setup>
</script>
<style scoped>
.child {
background-color: aquamarine;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
width: 200px;
height: 300px;
}
</style>
8.3 作用域插槽
数据在子组件,结构在父组件,需要传递数据给父组件
<template>
<div class="father">
<h2>父组件</h2>
<div class="content">
<Child1>
<template v-slot:s1>
<h2>今日游戏列表</h2>
</template>
<template v-slot="params">
<span>{{ params }}</span>
<ul>
<li v-for="item in params.youxi" :key="item.id">{{ item.name }}</li>
</ul>
</template>
</Child1>
<Child1>
<template v-slot:s1>
<h2>今日游戏列表</h2>
</template>
<template #default="params">
<span>{{ params }}</span>
<ol>
<li v-for="item in params.youxi" :key="item.id">{{ item.name }}</li>
</ol>
</template>
</Child1>
</div>
</div>
</template>
<script>
export default {
name: 'Father'
}
</script>
<script setup>
import { ref, reactive } from 'vue';
import Child1 from './Child1.vue';
let games = reactive([
'王者荣耀',
'绝地求生',
'天天酷跑'
])
let imgUrl = ref('http://47.113.201.132:9001/api/v1/download-shared-object/aHR0cDovLzEyNy4wLjAuMTo5MDAwL215bGlmZS8yMDI0LzA4LzE2L2NkNjY3YWY2YzAxMzRiNmZhZGZhMTMwZjk4NGQ0MGUxLmpwZz9YLUFtei1BbGdvcml0aG09QVdTNC1ITUFDLVNIQTI1NiZYLUFtei1DcmVkZW50aWFsPVNVTEdOWTY3RVpFMFoyVVZNVTFUJTJGMjAyNDA4MzAlMkZ1cy1lYXN0LTElMkZzMyUyRmF3czRfcmVxdWVzdCZYLUFtei1EYXRlPTIwMjQwODMwVDAxNTEzNVomWC1BbXotRXhwaXJlcz00MzE5NiZYLUFtei1TZWN1cml0eS1Ub2tlbj1leUpoYkdjaU9pSklVelV4TWlJc0luUjVjQ0k2SWtwWFZDSjkuZXlKaFkyTmxjM05MWlhraU9pSlRWVXhIVGxrMk4wVmFSVEJhTWxWV1RWVXhWQ0lzSW1WNGNDSTZNVGN5TlRBeU5UZ3lOQ3dpY0dGeVpXNTBJam9pY205dmRDSjkuOElYdU9CR2w3V2p5QWR5LUpoSVRMQml5dmJTRmhHM3lLNmZpUWE3RS1aaHQzV3o3TkdsMzdGUjlTal90aXdLNk4ySmxtcUhRNmtSQmxtODVzajNUa2cmWC1BbXotU2lnbmVkSGVhZGVycz1ob3N0JnZlcnNpb25JZD1udWxsJlgtQW16LVNpZ25hdHVyZT0xNjE5NmViYzcwZTM2MDkxNDQ4Y2ZmMmMzYTc3ODY4NzZmZTllNDhmYTZkMGQ5Y2JkMmU4NzE2NmM3MDQ4YWQw')
let videoUrl = ref('http://vjs.zencdn.net/v/oceans.mp4')
</script>
<style scoped>
h2 {
background-color: yellow;
text-align: center;
}
.father {
background-color: antiquewhite;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 10px;
}
.content {
display: flex;
justify-content: space-evenly;
}
img,
video {
width: 90%;
}
</style>
<template>
<div class="child1">
<slot name="s1"></slot>
<slot :youxi="games"></slot>
</div>
</template>
<script lang="ts" setup name="Child1">
import { reactive } from 'vue';
let games = reactive([
{
id: 111,
name: "王者荣耀"
},
{
id: 2222,
name: "QQ飞车"
},
{
id: 333,
name: "斗地主"
}
])
</script>
<style scoped>
.child1 {
background-color: aquamarine;
box-shadow: 0 0 10px;
border-radius: 10px;
padding: 20px;
width: 200px;
height: 300px;
}
</style>