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
  • 下麵是一個標準的IDistributedCache用例: public class SomeService(IDistributedCache cache) { public async Task<SomeInformation> GetSomeInformationAsync (string na ...
  • 這個庫提供了在啟動期間實例化已註冊的單例,而不是在首次使用它時實例化。 單例通常在首次使用時創建,這可能會導致響應傳入請求的延遲高於平時。在註冊時創建實例有助於防止第一次Request請求的SLA 以往我們要在註冊的時候實例單例可能會這樣寫: //註冊: services.AddSingleton< ...
  • 最近公司的很多項目都要改單點登錄了,不過大部分都還沒敲定,目前立刻要做的就只有一個比較老的項目 先改一個試試手,主要目標就是最短最快實現功能 首先因為要保留原登錄方式,所以頁面上的改動就是在原來登錄頁面下加一個SSO登錄入口 用超鏈接寫的入口,頁面改造後如下圖: 其中超鏈接的 href="Staff ...
  • Like運算符很好用,特別是它所提供的其中*、?這兩種通配符,在Windows文件系統和各類項目中運用非常廣泛。 但Like運算符僅在VB中支持,在C#中,如何實現呢? 以下是關於LikeString的四種實現方式,其中第四種為Regex正則表達式實現,且在.NET Standard 2.0及以上平... ...
  • 一:背景 1. 講故事 前些天有位朋友找到我,說他們的程式記憶體會偶發性暴漲,自己分析了下是非托管記憶體問題,讓我幫忙看下怎麼回事?哈哈,看到這個dump我還是非常有興趣的,居然還有這種游戲幣自助機類型的程式,下次去大玩家看看他們出幣的機器後端是不是C#寫的?由於dump是linux上的程式,剛好win ...
  • 前言 大家好,我是老馬。很高興遇到你。 我們為 java 開發者實現了 java 版本的 nginx https://github.com/houbb/nginx4j 如果你想知道 servlet 如何處理的,可以參考我的另一個項目: 手寫從零實現簡易版 tomcat minicat 手寫 ngin ...
  • 上一次的介紹,主要圍繞如何統一去捕獲異常,以及為每一種異常添加自己的Mapper實現,並且我們知道,當在ExceptionMapper中返回非200的Response,不支持application/json的響應類型,而是寫死的text/plain類型。 Filter為二方包異常手動捕獲 參考:ht ...
  • 大家好,我是R哥。 今天分享一個爽飛了的面試輔導 case: 這個杭州兄弟空窗期 1 個月+,面試了 6 家公司 0 Offer,不知道問題出在哪,難道是杭州的 IT 崩盤了麽? 報名面試輔導後,經過一個多月的輔導打磨,現在成功入職某上市公司,漲薪 30%+,955 工作制,不咋加班,還不捲。 其他 ...
  • 引入依賴 <!--Freemarker wls--> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.30</version> </dependency> ...
  • 你應如何運行程式 互動式命令模式 開始一個互動式會話 一般是在操作系統命令行下輸入python,且不帶任何參數 系統路徑 如果沒有設置系統的PATH環境變數來包括Python的安裝路徑,可能需要機器上Python可執行文件的完整路徑來代替python 運行的位置:代碼位置 不要輸入的內容:提示符和註 ...