常用场景: 父子传参
父组件
<template>
<div class="box">
<h1>组件间通信: props</h1>
<h3>父组件</h3>
<div>{{count}}</div>
<button @click="count++">修改父组件的值</button>
<Child :count="count" :changeCount="changeCount"></Child>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import Child from './Child.vue'
const count = ref(0)
// 修改父组件的值
const changeCount = (num=1)=>{
count.value+=num
}
</script>
子组件
<template>
<div class="box">
<h3>Child</h3>
<div>父组件传过来的参数:{{count}}</div>
<button @click="changeCount(3)">修改父组件的值</button>
</div>
</template>
<script lang="ts" setup>
// 接收父组件传过来的参数
defineProps<{
count:number
changeCount:(num : number)=>void
}>()
</script>
常用场景: 子给父传参
父组件
<template>
<div class="box">
<h1>组件间通信: custom_event</h1>
<!-- 绑定系统内置click -->
<!-- 元素绑click 系统事件,系统触发,可以拿到事件对象 -->
<button @click="clickHandler">按钮1</button>
<!-- 组件绑定系统事件,会自动绑定到子组件的根标签上 d当子组件没有根标签会被当成自定义事件 -->
<Event1 @click="compClickHandler"></Event1>
<!-- 自定义事件 -->
<!-- 组件绑xxx 自定义事件,在子组件中使用emit接收-->
<Event1 @xxx="xxxHandler" @yyy="yyyHandler"></Event1>
<!--
组件绑定 系统内置事件 跟vue2刚好相反 vue2需要使用.native ,vue3默认就是
组件绑定自定义事件,是语法上的差异
-->
</div>
</template>
<script lang="ts" setup>
import Event1 from "./Event1.vue";
const clickHandler = (e: Event) => {
console.log('点击了按钮', e)
}
const compClickHandler = (e: Event) => {
console.log('组件点击事件', e.target);
console.log('组件点击事件', e);
}
const xxxHandler = () => {
console.log('触发了xxx事件');
}
const yyyHandler = (e: string) => {
console.log('触发了yyy事件', e);
}
</script>
子组件
<template>
<div class="box">
<h3>Event1组件</h3>
<h4>子组件内容</h4>
<button @click="emit('xxx')">触发xxx事件</button>
<button @click="emit('yyy', '我爱你')">触发yyy事件</button>
</div>
</template>
<script lang="ts" setup>
const emit = defineEmits(['xxx', 'yyy'])
</script>
可以用于任意组件间的传参
父组件展示
<template>
<div class="box">
<h1>组件间通信: 消息订阅与发布</h1>
<!-- vue3没有总线,使用Pubsub跨组件通信 -->
<Child1 />
<Child2 />
</div>
</template>
<script lang="ts" setup>
import Child1 from './Child1.vue'
import Child2 from './Child2.vue'
</script>
子组件1
<template>
<div class="box">
<h3>Child1:接收数据</h3>
<div>{{message}}</div>
</div>
</template>
<script lang="ts" setup>
import { ref, onMounted } from 'vue'
import Pubsub from 'pubsub-js'
const message = ref('ooooo')
// 参数一:订阅的类型
// 参数二:我们的参数
onMounted(() => {
Pubsub.subscribe('gege', changeMessage)
})
const changeMessage = (type: string, str: string) => {
message.value = str
}
</script>
子组件2
<template>
<div class="box">
<h3>Child2</h3>
<button @click="Pubsub.publish('gege','啦啦啦')">修改Child1的msg</button>
</div>
</template>
<script lang="ts" setup>
import Pubsub from 'pubsub-js';
</script>
常用场景: 表单and组件
父组件
<template>
<div class="box">
<h1>组件间通信: v-model</h1>
<!-- vue3 组件实现v-model是 :modelValue 和 @update:modelValue -->
<!-- 绑定单个v-model -->
<CustomInput2 :modelValue="message" @update:modelValue="message=$event"></CustomInput2>
<CustomInput2 v-model="message"></CustomInput2>
<h2>绑定多个v-model</h2>
<!-- 绑定多个v-model -->
<CustomInput3 v-model="message" v-model:text="text"></CustomInput3>
</div>
</template>
<script lang="ts" setup>
import CustomInput2 from "./CustomInput2.vue";
import CustomInput3 from "./CustomInput3.vue";
import { ref } from 'vue'
const message = ref('gege')
const text = ref('keai')
</script>
子组件1
<template>
<div class="box">
<h4>Child2</h4>
<input type="text" :value="modelValue" @input="emit('update:modelValue',($event.target as HTMLInputElement).value)">
</div>
</template>
<script lang="ts" setup>
defineProps(['modelValue'])
const emit = defineEmits(['update:modelValue'])
</script>
子组件2–绑定多个v-model
<template>
<div class="box">
<h4>Child3</h4>
<input type="text" :value="text" @input="emit('update:text',($event.target as HTMLInputElement).value)">
<hr>
<input type="text" :value="modelValue" @input="emit('update:modelValue',($event.target as HTMLInputElement).value)">
</div>
</template>
<script lang="ts" setup>
defineProps(['modelValue', 'text'])
const emit = defineEmits(['update:modelValue', 'update:text'])
</script>
父组件
vue3 使用 const attrs = useAttrs(); 来获取到子组件接收的属性和事件,移除了 $listeners
注意: 可以接收到class和style了,defineProps接收过的数据和defineEmits接收过的事件, attrs没有
<template>
<div class="box">
<h1>组件间通信: attrs</h1>
<Child class="qwer" style="height: 80px;" aa="bb" :content="msg" @xxx="xxxHandler"></Child>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import Child from './Child.vue'
const msg = ref('哈哈哈哈')
const xxxHandler = () => {
console.log('xxx')
}
</script>
子组件
<template>
<div class="box">
<h3>Child</h3>
</div>
</template>
<script lang="ts" setup>
import {useAttrs} from 'vue'
defineProps(['content'])
const attr = useAttrs()
console.log(attr)
</script>
<style lang="less" scoped>
</style>
如图:被props接收过的参数content的数据没有 其他style class 以及事件都能够被接收到
应用场景
$refs:父组件访问子组件
$parent:子组件访问父组件
父组件
<template>
<div class="box">
<h1>组件间通信: $ref & $parent</h1>
<p>BABA有存款:{{ money }}</p>
<button @click="borrowMoneyFromXM(100)">找小明借钱100</button><br>
<br>
<Son ref="sonRef" />
<br>
<Daughter ref="dauRef" />
</div>
</template>
<script lang="ts" setup>
import Son from './Son.vue'
import Daughter from './Daughter.vue'
import { ref } from 'vue'
const money = ref(1000)
const btnRef = ref();
const sonRef = ref() // 获取组件实例: const声明的sonRef变量必须和 组件标签上ref的属性值一样
const dauRef = ref()
const borrowMoneyFromXM = (num :number)=>{
money.value+=num //父组件加钱
sonRef.value.money-=num //子组件减钱
// 父组件加钱
}
// 直接获取子组件改子组件数据选在不允许了,需要让子组件自己说明哪些数据是外部可以改动的
defineExpose({
money
})
</script>
子组件
<template>
<div class="box">
<h3>儿子小明: 有存款: {{ money }}</h3>
<button @click="giveMoney(50,$parent)">给BABA钱: 50</button>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue'
const money = ref(20000)
const giveMoney = (num :number,parent:any)=>{
money.value-=num
console.log('parent',parent)
parent.money+=num
}
defineExpose({
money
})
</script>
defineExpose说明
// setup 挂载到 script (ref默认是关闭的) 是不能直接使用ref 取到子组件的方法和变量
// 子组件需要使用defineExpose导出,父组件才能拿到
defineExpose({
money
})
应用场景
爷爷组件与孙子组件之间互相传参
爷爷组件
<template>
<div class="box">
<h1>组件间通信: provide + inject</h1>
<p>content1: {{ content1 }}</p>
<button @click="content1 += '--'">更新content1</button>
<p>content2.name: {{ content2.name }}</p>
<button @click="content2.name += '~~'">更新content2对象内的name</button>
<Child />
</div>
</template>
<script lang="ts" setup>
import { provide, reactive, ref } from 'vue';
import Child from './Child.vue'
const content1 = ref('jack')
const content2 = reactive({
name: 'tom',
})
const changeContent1 = () => {
content1.value = '杰克';
}
const changeContent2 = () => {
content2.name = '汤姆';
}
provide('content1',content1)
provide('content2',content2)
provide('changeContent1',changeContent1)
provide('changeContent2',changeContent2)
</script>
孙子组件
<template>
<div class="box">
<h1>组件间通信: provide + inject</h1>
<p>content1: {{ content1 }}</p>
<button @click="content1 += '--'">更新content1</button>
<p>content2.name: {{ content2.name }}</p>
<button @click="content2.name += '~~'">更新content2对象内的name</button>
<Child />
</div>
</template>
<script lang="ts" setup>
import { provide, reactive, ref } from 'vue';
import Child from './Child.vue'
const content1 = ref('jack')
const content2 = reactive({
name: 'tom',
})
const changeContent1 = () => {
content1.value = '杰克';
}
const changeContent2 = () => {
content2.name = '汤姆';
}
provide('content1',content1)
provide('content2',content2)
provide('changeContent1',changeContent1)
provide('changeContent2',changeContent2)
</script>
普通插槽 直接使用即可
具名插槽 具有名字的插槽
作用域插槽 在标签上绑定属性
绑定的数据在传递中是可以获取到的
具名插槽
<List>
<template v-slot:header>
<div>header-text</div>
</template>
</List>
<div>
<slot name="header">哈哈哈哈哈哈哈</slot>
</div>
作用域插槽
<template>
<div>
<List :data="todos">
<template v-slot="{ row, $index }">
<span :style="{ color: $index % 2 === 1 ? 'blue' : 'green' }">{{ $index + 1 }}--{{ row.text }}</span>
</template>
</List>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import List from './List.vue'
import type { Users } from './types';
const todos = ref<Users>([
{ id: 1, text: 'AAA', isComplete: false },
{ id: 2, text: 'BBB', isComplete: true },
{ id: 3, text: 'CCC', isComplete: false },
{ id: 4, text: 'DDD', isComplete: false },
])
</script>
<template>
<div class="box">
<ul>
<li v-for="(item, index) in data" :key="item.id">
<slot :row="item" :$index="index"></slot>
</li>
</ul>
</div>
</template>
<script lang="ts" setup>
import type { Users } from './types';
interface Props {
data: Users;
}
defineProps<Props>()
</script>