vue3 快速入门系列 - 组件通信

组件通信在开发中非常重要,通信就是你给我一点东西,我给你一点东西。

本篇将分析 vue3 中组件间的通信方式。

Tip:下文提到的绝大多数通信方式在 vue2 中都有,但是在写法上有一些差异。

准备环境

vue3 基础上进行。

新建三个组件:爷爷、父亲、孩子A、孩子B,在主页 Home.vue 中加载组件Gradfather.vue

<!-- Gradfather.vue -->
<template>
<p># 爷爷</p>
<hr>
<Father/>
</template> <script lang="ts" setup name="App">
import Father from './Father.vue';
</script>
<!-- Father.vue -->
<template>
<p># 父亲</p>
<hr>
<ChildA/>
<hr>
<ChildB/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue'
import ChildB from '@/views/ChildB.vue'
</script>
<!-- ChildA.vue -->
<template>
<p># 孩子A</p>
</template> <script lang="ts" setup name="App"> </script>
<!-- ChildB.vue -->
<template>
<p># 孩子B</p>
</template> <script lang="ts" setup name="App"> </script>

浏览器呈现:

# 爷爷
——————————————————
# 父亲
——————————————————
# 孩子A
——————————————————
# 孩子B

下文将再此基础上演示组件间的通信。

props

需求:实现父给子一件新衣服,子给父一个吻,都用 props 实现。

请看代码:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<p>来自孩子A: {{ b }}</p>
<hr>
// 传一个属性和一个方法
<ChildA :gift="a" :sendWen="getWen"/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' import {ref} from 'vue'
let a = ref('新衣服') let b = ref('') function getWen(val:string){
b.value = val
}
</script>
<!-- ChildA.vue -->
<template>
<p># 孩子A</p>
<p>来自父亲:{{ gift }}</p>
</template> <script lang="ts" setup name="App">
const props = defineProps(['gift', 'sendWen'])
// 调用方法,通过参数传递数据给父组件
props.sendWen('kiss')
</script>

页面呈现:

# 父亲

来自孩子A: kiss
————————————————————
# 孩子A 来自父亲:新衣服

子给父传数据借助了方法。

通常我们可能会用自定义事件来向父组件传递数据,但是在 react 中,子组件给父组件传递数据就是用 props 传递方法的这种方式进行的。

Tip:祖父给孙子传递就不要用 props。否则按照这个思路,无论什么情况都可以用这个方法。

自定义事件

请看示例:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<p>来自孩子A: {{ b }}</p>
<hr>
<!-- send-gift 肉串命名,一个单词就像一块肉 kebab-case。官方推荐 -->
<ChildA :gift="a" @send-gift="getGift"/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' import {ref} from 'vue'
let a = ref('新衣服2') let b = ref('') function getGift(val:string){
b.value = val
}
</script>

父组件通过 @send-gift="getGift" 给孩子绑定自定义事件,子组件通过 defineEmits 声明可以触发的事件,最后通过 emit('send-gift', 'kiss2') 触发事件,并将参数传过去。

<!-- ChildA.vue -->
<template>
<p># 孩子A</p>
<p>来自父亲:{{ gift }}</p>
</template> <script lang="ts" setup name="App">
defineProps(['gift',])
// 声明事件 - 定义一个组件可以发射(emit)的事件
const emit = defineEmits(['send-gift'])
emit('send-gift', 'kiss2') </script>

浏览器呈现:

# 爷爷
——————————————————
# 父亲 来自孩子A: kiss2
——————————————————
# 孩子A 来自父亲:新衣服2

Tip:我们推荐你始终使用 kebab-case 的事件名 —— vue2 官网 - 事件名

mitt

在 vue2 中我们学过中央事件总线

Vue 3中,中央事件总线(Vue 2中的emit/on机制)已被废除。Vue 3更加推崇使用组合 API、provide/inject以及props/emits来进行组件之间的通信。这样的做法使得组件通信更加明确和可追踪,并且更容易维护和理解。而像mitt这样的第三方库可以作为替代方案,用于实现更灵活的事件管理。

mitt 可以实现任意组件之间的通信。

pubsub(例如 pubsub-js 库)、$bus(例如 vue2 中的中央事件总线)、mitt 都是前端中常见的用于实现事件总线(Event Bus)或事件订阅-发布(Publish-Subscribe)模式的解决方案。这三者都是一个套路。也就是:

  • 接收数据:提前绑定(订阅数据)
  • 提供数据:适时触发(发布消息)

mitt 用法很简单,直接看 mitt 仓库。首先下载包:

PS hello_vue3>  npm install --save mitt

added 1 package, and audited 72 packages in 2s

10 packages are looking for funding
run `npm fund` for details 1 moderate severity vulnerability To address all issues, run:
npm audit fix Run `npm audit` for details.
"mitt": "^3.0.1"

创建 emitt 并在 main.ts 将其引入项目:

// src\utils\emitter.ts
import mitt from 'mitt' const emitter = mitt() export default emitter
// 引入
import emitter from './utils/emitter'

需求:现在我们让 ChildA 给 ChildB 送礼物。

请看实现:

ChildA 中触发事件:emitter.emit

<!-- ChildA.vue -->
<template>
<p># 孩子A</p>
<button @click="emitter.emit('send-toy', '篮球')">给兄弟礼物</button>
</template> <script lang="ts" setup name="App">
import emitter from '@/utils/emitter';
</script>

ChildB 中绑定事件:emitter.on

<!-- ChildB.vue -->
<template>
<p># 孩子B</p>
<p>兄弟送的礼物:{{ gift }}</p>
</template> <script lang="ts" setup name="App">
import emitter from '@/utils/emitter'; import {ref} from 'vue' let gift = ref('') // 如果将 any 改成 string,vscode 报错。暂时不知解决:
/*
没有与此调用匹配的重载。
第 1 个重载(共 2 个),“(type: "*", handler: WildcardHandler<Record<EventType, unknown>>): void”,出现以下错误。
第 2 个重载(共 2 个),“(type: "get-toy", handler: Handler<unknown>): void”,出现以下错误。ts(2769)
*/
emitter.on('send-toy', (e: any) => {
gift.value = e;
}); </script>

在 ChildA 中点击按钮,B就能收到礼物。完成任意组件的通信。

Tip: 建议组件卸载时解绑事件。就像这样:

import {onUnmounted} from 'vue'

onUnmounted(() => {
// 移除该类型的所有事件处理程序
emitter.off('send-toy')
})

其他写法有:

// 监听
// foo { a: 'b' }
emitter.on('foo', e => console.log('foo', e) )
// 触发
emitter.emit('foo', { a: 'b' })
// 监听所有事件。比如 foo2 就会触发
// foo2 {a: 'b'}
emitter.on('*', (type, e) => console.log(type, e) )
emitter.emit('foo2', { a: 'b' })
// 清除所有事件
emitter.all.clear()
// 注册和解绑事件
function onFoo() {}
emitter.on('foo', onFoo) // listen
emitter.off('foo', onFoo) // unlisten

v-model

vue2 中 v-model 用于简化父子之间的通信

你可能不会经常直接在自定义组件中编写 v-model,但是许多 UI 组件库的底层确实会使用 v-model 来简化父子组件之间的通信和数据流动。这种设计可以使得使用这些组件时更加方便和直观。

举例来说,当你使用一个 UI 组件库提供的输入框组件时,通常可以通过 v-model 来实现父组件与该输入框组件之间的双向绑定,让你可以直接在父组件中操作输入框的值,而不需要手动监听事件或者通过 props 和 emit 进行通信。这种方式大大简化了组件的使用方式和数据流动。

v-model 作用在 input 上可以实现双向绑定,作用在组件上,也能实现父子组件之间的通信(vue2 v-model数字输入框组件

v-model 实际上是语法糖,对于 input,等于绑定了 :value 和 @input。就像这样:

// vue2
<input v-model="message" placeholder="edit me">
等于
<input type="text" :value="message" @input="message = $event.target.value" placeholder="edit me">

vue3 中 v-model 类似,v-model 对应的是 modelValue 的 prop 和 update:modelValue 的事件。比如我想封装一个 MyInput 组件。

<MyInput v-model="username"/>

等价

<MyInput
:modelValue="username"
@update:modelValue="username = $event"
/>

需求:组件A使用 MyInput,通过 v-model 实现父子之间的通信。

首先不用语法糖,实现如下:

<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p>val: {{ val }}</p>
// 方式1
<MyInput :modelValue="val" @update:modelValue="changeVal"/>
</template> <script lang="ts" setup name="App">
import MyInput from '@/views/MyInput.vue'
import {ref} from 'vue'
let val = ref('p') function changeVal($event: string){
val.value = $event
}
</script>

Tipupdate:modelValue 就是事件名,只是包含一个冒号。

<template>
i am MyInput:
<p><input :value="val" @input="handleInput" /></p>
</template> <script lang="ts" setup name="App">
import { ref,toRefs } from 'vue'
const props = defineProps(['modelValue'])
const emits = defineEmits(['update:modelValue']) console.log('props: ', props);
// 组件接手初始值
// 注:之后父组件的 props 修改后,val 不会在响应,需要自己手动修改 val
let val = ref(props.modelValue) function handleInput($event: Event){
// 断言是一个 input 对象。否则ts报错:没有 value
val.value = (<HTMLInputElement>$event.target).value
emits('update:modelValue', val.value)
}
</script>

浏览器呈现:

# 组件A

val: p

i am MyInput:

// 这是 input 元素
p

编辑 input 内容时, val 对应的值也会同步,于是实现了父子之间的通信。

这三种方式在这里完全可以替换,于是我们知道 v-model 确实就是个语法糖。

// 方式1
<MyInput :modelValue="val" @update:modelValue="changeVal"/>
// 方式2:模板自动对 ref 进行解包
<!-- <MyInput :modelValue="val" @update:modelValue="val = $event"/> -->
// 方式3
<!-- <MyInput v-model="val"/> -->

重命名 modelValue

目前属性名和方法名中默认是 modelValue,就像:<MyInput :modelValue="val" @update:modelValue="changeVal"/>,希望重命名。

下面这个例子通过 v-model 同时传2个值,并修改默认值。请看示例:

<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p>val: {{ val }}</p>
<p>val2: {{ val2 }}</p>
<MyInput v-model:name="val" v-model:age="val2"/>
</template> <script lang="ts" setup name="App">
import MyInput from '@/views/MyInput.vue'
import {ref} from 'vue'
let val = ref('p')
let val2 = ref(18)
</script>
<template>
i am MyInput:
<p><input :value="name" @input=" emits('update:name', (<HTMLInputElement>$event.target).value)" /></p>
<p><input :value="age" @input=" emits('update:age', (<HTMLInputElement>$event.target).value)" /></p>
</template> <script lang="ts" setup name="App">
const props = defineProps(['name', 'age'])
const emits = defineEmits(['update:name', 'update:age']) </script>

$attrs

祖孙数据互传可以使用 $attrs 实现。

Tip:$attrs 详细请看:vue2 $attrs

父组件给子组件传递三个属性,子组件通过 props 接收一个,剩余2个属性就会到 $attrs:

<!-- Gradfather.vue -->
<template>
<p># 爷爷</p>
<hr>
<Father :name="name" :age="age" :tel="tel"/>
</template> <script lang="ts" setup name="App">
let name = ref('peng')
let age = ref(18)
let tel = ref('131xxx') // Vite 使用了 ES 模块的动态引入特性,允许在运行时动态加载模块,而不需要在编译时就确定所有的依赖关系。
// 这种特性使得在 <script setup> 块中将 import 放在尾部成为可能。
import Father from './Father.vue';
import {ref} from 'vue'
</script>
<!-- Father.vue -->
<template>
<p># 父亲</p>
<p>$attrs {{ $attrs }}</p>
<hr>
<ChildA/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' defineProps(['name']) </script>

接着用 $attrs 实现祖父给孙子传递数据。核心代码如下:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<p>$attrs {{ $attrs }}</p>
<hr>
<!-- v-bind 支持对象语法,这两行是等价的 -->
<ChildA v-bind="$attrs"/>
<!-- <ChildA :name="name" :age="$attrs.age"/> -->
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue'
</script>
<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p>$attrs: {{ $attrs }}</p>
<p>来自祖父的name: {{ name }}</p>
<p>来自祖父的age: {{ age }}</p>
</template> <script lang="ts" setup name="App">
defineProps(['name', 'age'])
</script>

浏览器呈现:

# 父亲

$attrs { "name": "peng", "age": 18, "tel": "131xxx" }
————————————————————————————————————————————————————————
# 组件A $attrs: { "tel": "131xxx" } 来自祖父的name: peng 来自祖父的age: 18

孙子给祖父传数据,利用 props 的方法,这里祖父提供一个修改电话的方法,孙子调用该方法即可。核心代码如下:

<!-- Gradfather.vue -->
<template>
<p># 爷爷</p>
<p>tel: {{ tel }}</p>
<hr>
<Father :name="name" :age="age" :tel="tel" :changeTel="changeTel"/>
</template> <script lang="ts" setup name="App">
function changeTel(v: string){
console.log('v: ', v); tel.value = v
}
</script>
<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p><button @click="changeTel('132')">change 祖父 tel</button></p>
</template> <script lang="ts" setup name="App">
defineProps(['name', 'age', 'changeTel'])
</script>

在孙子中点击按钮,祖父的 tel 就会改变。

Tip:孙子给祖父传递数据也可以用自定义事件的升级版本 $listener。

$refs 和 $parent

Tip: 在Vue.js 2.x中,$refs是一个特殊的属性,用于访问组件或DOM元素的引用。当在模板中使用ref属性给元素或组件命名时,Vue.js会自动生成一个$refs对象,其中包含了对这些元素或组件的引用。

需求:父给子一个玩具,子给父一个吻。

父组件通过 ref 给子组件一个玩具。请看代码:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<p><button @click="sendGift">给孩子礼物</button></p>
<hr>
<ChildA ref="c1"/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' import {ref} from 'vue'
const c1 = ref() function sendGift(){
// c1.value: Proxy(Object) {gift: RefImpl, __v_skip: true}
console.log('c1.value: ', c1.value);
c1.value.gift = '篮球'
} </script>
<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p>父亲给的礼物:{{ gift }}</p>
</template> <script lang="ts" setup name="App"> import {ref} from 'vue'
const gift = ref('')
// defineExpose 是一个用于在组合式 API 中将组件的属性或方法暴露给父组件的函数
defineExpose({gift})
</script>

当在模板中使用ref属性给元素或组件命名时,Vue.js会自动生成一个$refs对象。可以通过 $refs 给孩子礼物。请看示例:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<p><button @click="sendGift">通过 ref 给孩子礼物</button></p>
<p><button @click="test($refs)">通过 $refs 给孩子礼物</button></p>
// 注:空对象
<p>$refs: {{ $refs }}</p>
<hr>
<ChildA ref="c1"/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' import {ref} from 'vue'
const c1 = ref() function sendGift(){
...
} // {[key: string]: any} 表示对象的键是字符串类型,而值可以是任意类型
function test(v: {[key: string]: any}){
// v就是传来的 $refs
v.c1.gift = '足球'
console.log('v: ', v);
}
</script>

Tip:模板通点击可以将 $refs 传入js中,模板中直接通过 $refs 为空(或许是 $refs 是后生成的,并且没有响应式)。

疑惑:如何在vue3的组合式api的js里直接取得 $refs?

孩子给父亲礼物,可以使用 $parent,最终代码如下:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<p>孩子给的礼物:{{ gift }}</p>
<p><button @click="sendGift">通过 ref 给孩子礼物</button></p>
<hr>
<ChildA ref="c1"/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' import {ref} from 'vue'
const c1 = ref() function sendGift(){
console.log('c1.value: ', c1.value);
c1.value.gift = '篮球'
} let gift = ref('')
// 父亲只让别人访问gift,其他不允许
defineExpose({gift}) </script>
<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p>父亲给的礼物:{{ gift }}</p>
<p><button @click="sendGift($parent)">通过 $parent 给父亲礼物</button></p>
</template> <script lang="ts" setup name="App"> import {ref} from 'vue'
const gift = ref('') function sendGift(parent: any){
console.log('p: ', parent);
parent.gift = 'kiss'
}
defineExpose({gift})
</script>

Tip:ref($refs)、$parent 直接操作父组件或子组件的数据,不太好。但某些情况下或许有用。

provide 和 inject

上面我们使用 $arrts 实现了祖孙数据互传。有个缺点就是会打扰到中间人:父亲 —— 在父组件中需要写 v-bind=$attrs

而 provide/inject 不打扰中间人,实现祖孙数据互传。请看示例:

祖父通过 provide 提供属性或方法给后代:

<!-- Gradfather.vue -->
<template>
<p># 爷爷</p>
<hr>
<Father/>
</template> <script lang="ts" setup name="App">
import Father from './Father.vue';
import {ref,} from 'vue' function changeAddress(v: string){
address.value = v
} import { provide} from 'vue'
let address = ref('长沙')
provide('address', address)
provide('changeAddress', changeAddress) </script>

父组件通过 inject 能收到祖父提供出来的数据:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<!-- 父组件也能收到数据。provide 能传给所有后代,不仅仅是孙子 -->
<p>address {{ address }}</p>
<hr>
<ChildA/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' import { inject } from 'vue'; let address = inject('address')
</script>

孙子通过 inject 接收祖父提供的属性和方法:

<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p>address: {{ address }}</p>
<p><button @click="changeAddress('北京')">change 祖父 tel</button></p>
</template> <script lang="ts" setup name="App"> import { inject } from 'vue'; let address = inject('address')
// inject 第二个参数用于设置默认值,用于解决 ts 报错。
let changeAddress = inject('changeAddress', (v:string) => {})
</script>

浏览器呈现:

# 爷爷
——————————————————————
# 父亲 address 长沙
——————————————————————
# 组件A address: 长沙 // 按钮
change 祖父 tel

长沙来自祖父。点击按钮,长沙变成北京,实现孙子到祖父的通信。

升级一下上述示例:祖父提供对象,并将地址和修改地址的方法合并一起传出。请看示例:

<!-- Gradfather.vue -->
<template>
<p># 爷爷</p>
<hr>
<Father/>
</template> <script lang="ts" setup name="App">
import Father from './Father.vue';
import {reactive, ref,} from 'vue' function changeAddress(v: string){
address.value = v
} import { provide} from 'vue'
let address = ref('长沙') let phone = reactive({
price: 1800,
color: 'red'
})
// 注:不要 address.value,否则就不是响应式,孩子的address不会变。
provide('addressContext', {address, changeAddress}) // 传对象
provide('phone', phone) </script>
<!-- Father.vue -->
<template>
<p># 父亲</p>
<p>phone.color: {{ phone.color }}</p>
<hr>
<ChildA/>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue'
import { inject } from 'vue';
// 隐晦的告诉模板中的 phone.color 是字符串类型
let phone = inject('phone', {color: '', price: 0})
</script>
<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<p>address: {{ address }}</p>
<p><button @click="changeAddress('北京')">change 祖父 tel</button></p>
</template> <script lang="ts" setup name="App"> import { inject } from 'vue'; let {address, changeAddress} = inject('addressContext', {address: '', changeAddress: (v:string) => {}})
// address: RefImpl {__v_isShallow: false, dep: undefined, __v_isRef: true, _rawValue: '长沙', _value: '长沙'}
// address 已经是响应式的。无需使用 toRefs
console.log('address: ', address);
</script>

插槽

vue2 中就存在这个概念,详细请看官网:vue3 插槽

具名插槽和默认插槽用于父传子,作用域插槽用于子传父

默认插槽

子组件通过 slot 定义插槽。

比较简单,直接看例子:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<hr>
<ChildA>
click me
</ChildA>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue'
</script>
<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<slot>默认值</slot>
</template>

如果没传,则显示“默认值”

Tip:父组件使用子组件,比如子组件有标题和内容,标题通过父组件 props 传递,内容可以是图片、视频,列表等等,就可以在父组件中使用插槽。

具名插槽

默认插槽其实就是具名插槽的一种。因为默认插槽也有名字(即default)。

<!-- Father.vue -->
<template>
<p># 父亲</p>
<hr>
<ChildA>
<!-- 这边顺序随便(先写 list2 再写 list1),最终渲染顺序由子组件中的 具名slot 决定(先渲染 list1) -->
<template #list2>
<ul>
<li>c</li>
<li>d</li>
</ul>
</template> <!-- 报错:<ul v-slot:list> -->
<template v-slot:list1>
<ul>
<li>a</li>
<li>b</li>
</ul>
</template> <template #default>
默认插槽的名字叫 default
</template>
</ChildA>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue' </script>

通过 name 属性给插槽定义名字,父组件通过 v-slot 应用对应的插槽。

现在用 v-slot,只能用于组件或 <template> 标签。用于组件的缺点是:标签内的全部内容会放在具名插槽上,如果存在多个具名插槽就不行了。

v-slot:list1 简写成 #list1

Tip:slot-scope 在2.6废除了,而在 2.6.x 中,scope、slot和slot-scope 都推荐使用 v-scope。

<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<!-- 定义插槽名字-->
<slot name="list1"></slot>
<slot name="list2"></slot> <slot></slot>
</template>

浏览器呈现大概这样:

# 组件A

- a
- b - c
- d 默认插槽的名字叫 default

作用域插槽

作用域插槽(scoped slots)的主要作用是允许父组件在插槽内容中访问子组件中的数据或方法。

数据在子组件,但数据生成的结构,由父组件决定

作用域插槽,UI组件库用的很多,比如 table、对话框。写过 table的通常会用插槽。表格某列的结构由我们决定。数据我们会传给ui组件。

Tip:为什么叫作用域插槽?可以这么理解:父组件中需要访问孩子的数据,但是有作用域的限制,于是用这个作用域插槽解决。

作用域插槽有点子传父的感觉。因为在父组件中用到了子组件的数据

请看这个简单的示例:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<hr>
<ChildA>
<template v-slot="myProps">
<div>
父组件定义结构,数据来自孩子:{{ myProps }}
</div> </template>
</ChildA>
</template> <script lang="ts" setup name="App">
import ChildA from '@/views/ChildA.vue'
</script>
<!-- ChildA.vue -->
<template>
<p># 组件A</p>
<slot :age="age"></slot>
</template> <script lang="ts" setup name="App">
import {ref} from 'vue' let age = ref(18)
</script>

浏览器呈现:

# 组件A

父组件定义结构,数据来自孩子
{ "age": 18 }

Tip:有人觉得作用域插槽难,其实是因为写法多。比如:

可以直接解构:

<ChildA>
<template v-slot="{age}">
父组件定义结构,数据来自孩子:{{ age }}
</template>
</ChildA>

在加上具名插槽:

<ChildA>
<!-- 简写: <template #p1="{age}"> -->
<template v-slot:p1="{age}">
父组件定义结构,数据来自孩子:{{ age }}
</template>
</ChildA>

总结

  • 父传子:props、v-model、$refs、插槽
  • 子传父:props、自定义事件、v-model、$parent、作用域插槽
  • 祖孙互传:$attrs、provide/inject
  • 兄弟和任意组件:mitt、pinia(Pinia 是一个基于 Vue 3 的状态管理库,可代替vuex,配合 vue3 使用)

扩展

v-bind

v-bind 最基本用途是动态更新html元素上的属性,比如 id

div v-bind:id="dynamicId"></div>

<!-- 缩写成 -->
<div :id="dynamicId"></div>

还可以写对象:

<a-form-model v-bind="{a: 100, b:200}">

等价于

<a-form-model
:a="100"
:b="200"
>

事件传参

先复习下vue2 事件传参

<!-- 什么都不传 -->
<button v-on:click="greet()">Greet</button>
<!-- 默认会传递一个原生事件对象 event -->
<button v-on:click="greet">Greet</button>
<!-- $event 是Vue 提供的一个特殊变量,表示原生事件对象 -->
<button v-on:click="greet('hello', $event)">Greet</button>

vue3 中也一样。请看示例:

<!-- Father.vue -->
<template>
<p># 父亲</p>
<p><button @click="test(1,2)">test(1,2)</button></p>
<!-- $event 就是一个占位符,会传入事件对象 -->
<p><button @click="test(1,2, $event)">test(1,2, $event)</button></p>
<p><button @click="test2">test2</button></p> </template> <script lang="ts" setup name="App"> // c 是undefined
function test(a: number, b: number, c?: Event){
console.log('a', a, 'b', b)
console.log('c', c)
}
//
function test2(a:Event){
// a PointerEvent {isTrusted: true, _vts: 1713164112622, pointerId: 1, width: 1, height: 1, …}
console.log('a', a)
}
</script>

Tip$event 是一个特殊的占位符,比如这样也会触发:@click="a = $event"

$event 能否 .target

对于原生事件,$event是事件对象,就能 $event.target.value

对于自定义事件,$event就是触发事件时传来的数据,就不能 .target

ref

访问 ref 数据到底要不要 .value?

如果ref 是你定义的,例如 let name = ref('Peng'),读取name就得加 .value,如果你要访问的 ref 是某个响应式数据内的属性,就不要 .value。就像这样:

let obj = reactive({
name: 'Peng',
o: ref('18')
})
// Peng 18
console.log(obj.name, obj.o);

vscode 报错如何查看

用vscode 编码时,有时会出现红色波浪线,移上去有很多提示。看你能看懂的。比如中间是很多代码,最后一点中文,可能通过中文你就知道报错原因。

vue3 快速入门系列 —— 组件通信的更多相关文章

  1. 快速入门系列--WebAPI--01基础

    ASP.NET MVC和WebAPI已经是.NET Web部分的主流,刚开始时两个公用同一个管道,之后为了更加的轻量化(WebAPI是对WCF Restful的轻量化),WebAPI使用了新的管道,因 ...

  2. [转]快速入门系列--WebAPI--01基础

    本文转自:http://www.cnblogs.com/wanliwang01/p/aspnet_webapi_base01.html ASP.NET MVC和WebAPI已经是.NET Web部分的 ...

  3. vue 快速入门 系列 —— vue 的基础应用(上)

    其他章节请看: vue 快速入门 系列 vue 的基础应用(上) Tip: vue 的基础应用分上下两篇,上篇是基础,下篇是应用. 在初步认识 vue一文中,我们已经写了一个 vue 的 hello- ...

  4. vue 快速入门 系列 —— vue 的基础应用(下)

    其他章节请看: vue 快速入门 系列 vue 的基础应用(下) 上篇聚焦于基础知识的介绍:本篇聚焦于基础知识的应用. 递归组件 组件是可以在它们自己的模板中调用自身的.不过它们只能通过 name 选 ...

  5. vue 快速入门 系列 —— vue-cli 上

    其他章节请看: vue 快速入门 系列 Vue CLI 4.x 上 在 vue loader 一文中我们已经学会从零搭建一个简单的,用于单文件组件开发的脚手架:本篇,我们将全面学习 vue-cli 这 ...

  6. 快速入门系列--WebAPI--03框架你值得拥有

    接下来进入的是俺在ASP.NET学习中最重要的WebAPI部分,在现在流行的互联网场景下,WebAPI可以和HTML5.单页应用程序SPA等技术和理念很好的结合在一起.所谓ASP.NET WebAPI ...

  7. 快速入门系列--MVC--01概述

    虽然使用MVC已经不少年,相关技术的学习进行了多次,但是很多技术思路的理解其实都不够深入.其实就在MVC框架中有很多设计模式和设计思路的体现,例如DependencyResolver类就包含我们常见的 ...

  8. WPF快速入门系列(1)——WPF布局概览

    一.引言 关于WPF早在一年前就已经看过<深入浅出WPF>这本书,当时看完之后由于没有做笔记,以至于我现在又重新捡起来并记录下学习的过程,本系列将是一个WPF快速入门系列,主要介绍WPF中 ...

  9. 前端学习 node 快速入门 系列 —— 初步认识 node

    其他章节请看: 前端学习 node 快速入门 系列 初步认识 node node 是什么 node(或者称node.js)是 javaScript(以下简称js) 运行时的一个环境.不是一门语言. 以 ...

  10. vue 快速入门 系列 —— 虚拟 DOM

    其他章节请看: vue 快速入门 系列 虚拟 DOM 什么是虚拟 dom dom 是文档对象模型,以节点树的形式来表现文档. 虚拟 dom 不是真正意义上的 dom.而是一个 javascript 对 ...

随机推荐

  1. CYQ.Data 支持 KingbaseES人大金仓数据库

    KingbaseES人大金仓数据库介绍: KingbaseES是一种关系型数据库管理系统,也被称为人大金仓数据库.KingbaseES 是北京人大金仓信息技术股份有限公司研发的,具有自主知识产权的通用 ...

  2. vue peek 解决了 vue-template 加载 相对目录 ./components 组件内容 vscode

    点击组件跳转,vue-helper 带@的能跳转,相对目录的不能跳转.vue peek 解决了这个问题.

  3. SourceTree 摘樱桃 === 遴选 [不要使用这个功能!!不要使用!不要使用!]

    SourceTree 摘樱桃 === 遴选 不要使用摘樱桃!!不要使用!不要使用! 我找了一个文本的git,进行的测试,发现很不好用,文档我又恢复过来了,因为就改了几个字,代码的话,会造成 不可挽回的 ...

  4. js之实现页面内所有图片旋转

    javascript:R=0; x1=.1; y1=.05; x2=.25; y2=.24; x3=1.6; y3=.24; x4=300; y4=200; x5=300; y5=200; DI=do ...

  5. 记录--九个超级好用的 Javascript 技巧

    这里给大家分享我在网上总结出来的一些知识,希望对大家有所帮助 前言 在实际的开发工作过程中,积累了一些常见又超级好用的 Javascript 技巧和代码片段,包括整理的其他大神的 JS 使用技巧,今天 ...

  6. power quyer 批量合并同一文件夹下数据格式相同的Excel文件

    一.需求描述:现在有一批数据格式相同的Excel文件需要把里面的内容合并到同一个Excel的一个sheet里面 二.新建一个叫数据汇总的Excel文件-数据-新建查询-从文件-选择数据存放的文件夹-然 ...

  7. 《Go程序设计语言》学习笔记之map

    <Go程序设计语言>学习笔记之map 一. 环境 Centos8.5, go1.17.5 linux/amd64 二. 概念 1) 散列表,是一个拥有键值对元素的无序集合.在这个集合中,键 ...

  8. 摄影系列:李涛ps视频教程笔记

    四种颜色模式: HSB:人眼的识别. RGB:基于光.(RGB自然三原色,三个最大值,得出白色,所以RGB为加色模式) CMY:基于印刷.(青.品.黄印刷三原色,三个最大值,得出黑色,所以CMY为减色 ...

  9. kingbaseES V8R6 备份恢复案例 -- sys_rman备份“DSO support..."故障

    案例说明: 在通过sys_rman执行备份时,出现"DSO support...."错误,如下图所示: sys_log日志: 适用版本: KingbaseES V8R6 一.问题分 ...

  10. 求一个整数的因数分解--Java--小白必懂

    public class OJ_1415 { public static void main(String[] args) { Scanner sc = new Scanner(System.in); ...