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
  • 最近做項目過程中,使用到了海康相機,官方只提供了C/C++的SDK,沒有搜尋到一個合適的封裝了的C#庫,故自己動手,簡單的封裝了一下,方便大家也方便自己使用和二次開發 ...
  • 前言 MediatR 是 .NET 下的一個實現消息傳遞的庫,輕量級、簡潔高效,用於實現進程內的消息傳遞機制。它基於中介者設計模式,支持請求/響應、命令、查詢、通知和事件等多種消息傳遞模式。通過泛型支持,MediatR 可以智能地調度不同類型的消息,非常適合用於領域事件處理。 在本文中,將通過一個簡 ...
  • 前言 今天給大家推薦一個超實用的開源項目《.NET 7 + Vue 許可權管理系統 小白快速上手》,DncZeus的願景就是做一個.NET 領域小白也能上手的簡易、通用的後臺許可權管理模板系統基礎框架。 不管你是技術小白還是技術大佬或者是不懂前端Vue 的新手,這個項目可以快速上手讓我們從0到1,搭建自 ...
  • 第1章:WPF概述 本章目標 瞭解Windows圖形演化 瞭解WPF高級API 瞭解解析度無關性概念 瞭解WPF體繫結構 瞭解WPF 4.5 WPF概述 ​ 歡迎使用 Windows Presentation Foundation (WPF) 桌面指南,這是一個與解析度無關的 UI 框架,使用基於矢 ...
  • 在日常開發中,並不是所有的功能都是用戶可見的,還在一些背後默默支持的程式,這些程式通常以服務的形式出現,統稱為輔助角色服務。今天以一個簡單的小例子,簡述基於.NET開發輔助角色服務的相關內容,僅供學習分享使用,如有不足之處,還請指正。 ...
  • 第3章:佈局 本章目標 理解佈局的原則 理解佈局的過程 理解佈局的容器 掌握各類佈局容器的運用 理解 WPF 中的佈局 WPF 佈局原則 ​ WPF 視窗只能包含單個元素。為在WPF 視窗中放置多個元素並創建更貼近實用的用戶男面,需要在視窗上放置一個容器,然後在這個容器中添加其他元素。造成這一限制的 ...
  • 前言 在平時項目開發中,定時任務調度是一項重要的功能,廣泛應用於後臺作業、計劃任務和自動化腳本等模塊。 FreeScheduler 是一款輕量級且功能強大的定時任務調度庫,它支持臨時的延時任務和重覆迴圈任務(可持久化),能夠按秒、每天/每周/每月固定時間或自定義間隔執行(CRON 表達式)。 此外 ...
  • 目錄Blazor 組件基礎路由導航參數組件參數路由參數生命周期事件狀態更改組件事件 Blazor 組件 基礎 新建一個項目命名為 MyComponents ,項目模板的交互類型選 Auto ,其它保持預設選項: 客戶端組件 (Auto/WebAssembly): 最終解決方案裡面會有兩個項目:伺服器 ...
  • 先看一下效果吧: isChecked = false 的時候的效果 isChecked = true 的時候的效果 然後我們來實現一下這個效果吧 第一步:創建一個空的wpf項目; 第二步:在項目裡面添加一個checkbox <Grid> <CheckBox HorizontalAlignment=" ...
  • 在編寫上位機軟體時,需要經常處理命令拼接與其他設備進行通信,通常對不同的命令封裝成不同的方法,擴展稍許麻煩。 本次擬以特性方式實現,以兼顧維護性與擴展性。 思想: 一種命令對應一個類,其類中的各個屬性對應各個命令段,通過特性的方式,實現其在這包數據命令中的位置、大端或小端及其轉換為對應的目標類型; ...