其他章節請看:
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>
Tip:update: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 快速入門 系列