vue3 快速入門系列 —— 組件通信

来源:https://www.cnblogs.com/pengjiali/p/18141648
-Advertisement-
Play Games

vue3 快速入門系列 - 組件通信 組件通信在開發中非常重要,通信就是你給我一點東西,我給你一點東西。 本篇將分析 vue3 中組件間的通信方式。 Tip:下文提到的絕大多數通信方式在 vue2 中都有,但是在寫法上有一些差異。 準備環境 在 vue3 基礎上進行。 新建三個組件:爺爺、父親、孩子 ...


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 編碼時,有時會出現紅色波浪線,移上去有很多提示。看你能看懂的。比如中間是很多代碼,最後一點中文,可能通過中文你就知道報錯原因。

作者:彭加李
出處:https://www.cnblogs.com/pengjiali/p/18141648
本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 介紹 MpChart是一個包含各種類型圖表的圖表庫,主要用於業務數據彙總,例如銷售數據走勢圖,股價走勢圖等場景中使用,方便開發者快速實現圖表UI。本示例主要介紹如何使用三方庫MpChart實現柱狀圖UI效果。如堆疊數據類型顯示,Y軸是否顯示,左Y軸位置,右Y軸位置,是否顯示X軸,是否繪製背景色,是否 ...
  • 條件語句用於基於不同的條件來執行不同的動作。 TypeScript 條件語句是通過一條或多條語句的執行結果(True 或 False)來決定執行的代碼塊。 可以通過下圖來簡單瞭解條件語句的執行過程: 條件語句 通常在寫代碼時,您總是需要為不同的決定來執行不同的動作。您可以在代碼中使用條件語句來完成該 ...
  • 一、Image 在HarmonyOS中,Image組件是用於顯示圖像文件的UI組件。它可以顯示本地圖像文件或遠程URL地址的圖像文件。Image組件的實現方式比較簡單,只需提供圖像文件路徑或URL地址即可。 Image通過調用介面來創建,介面調用形式如下: Image(src: string | ...
  • 介紹 本示例介紹用過使用ListItem組件屬性swipeAction實現列表左滑編輯效果的功能。 該場景多用於待辦事項管理、文件管理、備忘錄的記錄管理等。 效果圖預覽 使用說明: 點擊添加按鈕,選擇需要添加的待辦事項。 長按待辦事項,點擊刪除後,被勾選待辦事項被刪除。 左滑單個待辦事項,點擊刪除按 ...
  • 前言 最近有粉絲找到我,說被面試官給問懵了。 粉絲:面試官上來就問“一個vue文件是如何渲染成瀏覽器上面的真實DOM?”,當時還挺竊喜這題真簡單。就簡單說了一下先是編譯成render函數、然後根據render函數生成虛擬DOM,最後就是根據虛擬DOM生成真實DOM。按照正常套路面試官接著會問vue響 ...
  • 最近,群里聊到了一個很有意思的佈局效果。大致效果如下所示,希望使用 CSS 實現如下所示的佈局效果: 正常而言,我們的 HTML 結構大致是如下所示: <div class="g-container"> <div class="g-nav"> <ul> <li>Tab 1</li> <li>Tab ...
  • 一、前期轉杯 確保電腦上已安裝 node.js。 可通過命令 npm --version進行查詢,如果展示了版本號,則說明已安裝,若提示 npm 不是有內部或外部命令,也不是可運行的程式,則說明未安裝,可進入官網下載併進行安裝。 確保已安裝 Vue CLI。 可通過命令 vue --V 查看版本號, ...
  • Commonjs 什麼是 CommonJs CommonJs 是 js 模塊化的社區規範 模塊化產生的原因 隨著前端頁面複雜度的提升,依賴的第三方庫的增加,導致的 js 依賴混亂,全局變數的污染,和命名衝突 單個 js 文件內容太多,導致了維護困難,拆分成為多個文件又會發生第一點描述的問題 v8 引 ...
一周排行
    -Advertisement-
    Play Games
  • GoF之工廠模式 @目錄GoF之工廠模式每博一文案1. 簡單說明“23種設計模式”1.2 介紹工廠模式的三種形態1.3 簡單工廠模式(靜態工廠模式)1.3.1 簡單工廠模式的優缺點:1.4 工廠方法模式1.4.1 工廠方法模式的優缺點:1.5 抽象工廠模式1.6 抽象工廠模式的優缺點:2. 總結:3 ...
  • 新改進提供的Taurus Rpc 功能,可以簡化微服務間的調用,同時可以不用再手動輸出模塊名稱,或調用路徑,包括負載均衡,這一切,由框架實現並提供了。新的Taurus Rpc 功能,將使得服務間的調用,更加輕鬆、簡約、高效。 ...
  • 本章將和大家分享ES的數據同步方案和ES集群相關知識。廢話不多說,下麵我們直接進入主題。 一、ES數據同步 1、數據同步問題 Elasticsearch中的酒店數據來自於mysql資料庫,因此mysql數據發生改變時,Elasticsearch也必須跟著改變,這個就是Elasticsearch與my ...
  • 引言 在我們之前的文章中介紹過使用Bogus生成模擬測試數據,今天來講解一下功能更加強大自動生成測試數據的工具的庫"AutoFixture"。 什麼是AutoFixture? AutoFixture 是一個針對 .NET 的開源庫,旨在最大程度地減少單元測試中的“安排(Arrange)”階段,以提高 ...
  • 經過前面幾個部分學習,相信學過的同學已經能夠掌握 .NET Emit 這種中間語言,並能使得它來編寫一些應用,以提高程式的性能。隨著 IL 指令篇的結束,本系列也已經接近尾聲,在這接近結束的最後,會提供幾個可供直接使用的示例,以供大伙分析或使用在項目中。 ...
  • 當從不同來源導入Excel數據時,可能存在重覆的記錄。為了確保數據的準確性,通常需要刪除這些重覆的行。手動查找並刪除可能會非常耗費時間,而通過編程腳本則可以實現在短時間內處理大量數據。本文將提供一個使用C# 快速查找並刪除Excel重覆項的免費解決方案。 以下是實現步驟: 1. 首先安裝免費.NET ...
  • C++ 異常處理 C++ 異常處理機制允許程式在運行時處理錯誤或意外情況。它提供了捕獲和處理錯誤的一種結構化方式,使程式更加健壯和可靠。 異常處理的基本概念: 異常: 程式在運行時發生的錯誤或意外情況。 拋出異常: 使用 throw 關鍵字將異常傳遞給調用堆棧。 捕獲異常: 使用 try-catch ...
  • 優秀且經驗豐富的Java開發人員的特征之一是對API的廣泛瞭解,包括JDK和第三方庫。 我花了很多時間來學習API,尤其是在閱讀了Effective Java 3rd Edition之後 ,Joshua Bloch建議在Java 3rd Edition中使用現有的API進行開發,而不是為常見的東西編 ...
  • 框架 · 使用laravel框架,原因:tp的框架路由和orm沒有laravel好用 · 使用強制路由,方便介面多時,分多版本,分文件夾等操作 介面 · 介面開發註意欄位類型,欄位是int,查詢成功失敗都要返回int(對接java等強類型語言方便) · 查詢介面用GET、其他用POST 代碼 · 所 ...
  • 正文 下午找企業的人去鎮上做貸後。 車上聽同事跟那個司機對罵,火星子都快出來了。司機跟那同事更熟一些,連我在內一共就三個人,同事那一手指桑罵槐給我都聽愣了。司機也是老社會人了,馬上聽出來了,為那個無辜的企業經辦人辯護,實際上是為自己辯護。 “這個事情你不能怪企業。”“但他們總不能讓銀行的人全權負責, ...