組件通信
組件關系 | 傳遞方式 |
---|---|
父傳子 | 1. props 2. v-model 3. $refs 4. 默認插槽、具名插槽 |
子傳父 | 1.props 2.自定義事件3.v-model 4.parent 5.作用域插槽 |
祖傳孫、孫傳祖 | 1.$attrs 2.provide 、inject |
兄弟間、任意組件間 | 1.mitt 2.pinia |
【props】
概述:props
是使用頻率最高的一種通信方式,常用與:父<=>子。
- 若父傳子:屬性值是非函數。
- 若子傳父:屬性值是函數。
父組件:
<template>
<div class="father">
<h3>父組件,</h3>
<h4>我的車:{{car}}</h4>
<h4>兒子給的玩具:{{toy}}</h4>
<Child car="car" getToy="getToy"/>
</div>
</template>
<script setup lang="ts"name="Father">
import Child from './Child.vue
import ref from "vue";
//數據
const car=ref('奔馳')
const toy =ref()
//方法
function getToy(value:string){toy.value=value
}
</script>
<style scoped>
.father{
background-color:rgb(165,164,164);
padding:20px;
border-radius:10px;
}
</style>
<template>
<div class="child">
<h3>子組件</h3>
<h4>玩具:{{ toy }}</h4>
<h5>父給的車:{{ car }}</h5>
<button @click="send">把玩具交給父親</button>
</div>
</template>
<script setup lang="ts" name="Child">import { ref } from 'vue';const toy = ref('奧特曼')defineProps(['car','sendToy'])</script>
<style scoped>
.child{
background-color:skyblue;
padding:10px;
box-shadow:00 10px black;
border-radius:10px;
}
</style>
【custom-event】
<template><div class="father"><h3>父組件</h3><h4 v-show="toy">子給的玩具:{{ toy }}</h4><!--給子組件Child綁定事件 --><Child @send-toy="getToy" /></div>
</template><script setup lang="ts" name="Father">
import { ref } from 'vue';
import Child from './Children.vue';
const toy = ref('奧特曼');
// 接收子組件傳來的 toy
function getToy(value:string){
console.log('父',value)
toy.value = value
}
</script>
<template>
<div class="child">
<h3>子組件</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emit('send-toy',toy)">點擊</button>
</div>
</template>
<script setup lang="ts" name="ChildEvent">
import { ref } from 'vue'
const toy = ref('奧特曼');
const emit=defineEmits(['send-toy']);
</script>
<style scoped>
.child{
background-color:skyblue;
padding:10px;
box-shadow:00 10px black;
border-radius:10px;
}
</style>
【mitt】
<template><div class="child2"><h3>子組件2</h3><h4>電腦:{{ computer }}</h4><h5>哥哥給的玩具:{{ toy }}</h5></div>
</template><script setup lang="ts" name="Child2">
import emitter from '@/tools/emitter'
import { onUnmounted, ref } from 'vue'const computer = ref('小米')
const toy = ref('')// 給emitter綁定事件
emitter.on('send-toy', (value:unknown) => {if (typeof value === 'string') {toy.value = value}
})onUnmounted(() => {emitter.off('send-toy')
})
</script>
<template>
<div class="child1">
<h3>子組件1</h3>
<h4>玩具:{{ toy }}</h4>
<button @click="emitter.emit('send-toy',toy)">玩具給弟弟</button>
</div>
</template>
<script setup lang="ts" name='Child1'>
import emitter from '@/tools/emitter';
import { ref } from 'vue'const toy=ref('奧特曼')
</script>
【v-model】
<template><div class="father"><h3>父組件</h3><h4>{{ username }}</h4><!-- v-model用在html標簽上 --><!-- <input type="text" v-model="username"> --><!-- <input type="text" :value="username" @input="username = (<HTMLInputElement>$event.target).value"> --><!-- v-model用在組件標簽上 --><HISTInput v-model="username"></HISTInput><!-- <HISTInput:modelValue="username"@update:modelValue="username=$event"></HISTInput> --></div>
</template>
<template><input type="text":value="modelValue"@input="emit('update:modelValue', (<HTMLInputElement>$event.target).value)">
</template><script setup lang="ts" name="HISTInput">
defineProps(['modelValue'])
const emit=defineEmits(['update:modelValue'])
</script>
【$attrs】
- 概述:
$attrs
用于實現當前組件的父組件,向當前組件的子組件通信**(祖一孫)**。 - 具體說明:
$attrs
是一個對象,包含所有父組件傳入的標簽屬性。
注意:
$attrs
會自動排除props
中聲明的屬性(可以認為聲明過的props
被子組件自己“消費”了)
<template><div></div>
</template><script setup lang="ts" name="GrandChild"></script><style lang="scss" scoped></style>
<template><div class="Child"><h3>孫組件</h3><h4>a:{{ a }}</h4><h4>b:{{ b }}</h4><h4>]c:{{ c }}</h4><h4>d:{{ d }}</h4><h4>x:{{ x }}</h4><h4>y:{{ y }}</h4><h4>z:{{ z }}</h4><button @click="updateA(6)">點我加6</button><GrandChild v-bind="$attrs" /></div>
</template><script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue';
defineProps(['a','b','c','d','x','y','z','updateA'])
</script><style lang="scss" scoped></style>
<template><div class="father"><h3>父組件</h3><h4>a:{{ a }}</h4><h4>b:{{ b }}</h4><h4>c:{{ c }}</h4><h4>d:{{ d }}</h4><Child :a="a" :b="b" :c="c" :d="d" v-bind="{x:100,y:200}" :updateA="updateA"/></div>
</template><script setup lang="ts" name="Father">
import Child from './ChildPractice.vue'
import { ref } from 'vue'const a=ref(1)
const b=ref(2)
const c=ref(3)
const d=ref(4)
function updateA(value:number){a.value=value
}
</script><style lang="scss" scoped></style>
【refs、refs、refs、parent】
- 概述:
$refs
用于:父→子。$parent
用于:子一父。
- 原理如下:
屬性 | 說明 |
---|---|
$refs | 值為對象,包含所有被ref屬性標識的D0M元素或組件實例。 |
$parent | 值為對象,當前組件的父組件實例對象。 |
<template><div class="father"><h2>父組件</h2><h4>房產:{{ house }}</h4><button @click="changeToy">修改Child1的玩具</button><button @click="changeComputer">修改Child1的玩具</button><button @click="getAllChild($refs)">獲取所有的子組件實例對象</button><Child1 ref="c1"></Child1><Child2></Child2></div>
</template><script setup lang="ts" name="FatherPractice">
import { ref } from 'vue';
import Child1 from './Child1Practice.vue';
import Child2 from './Child2Practice.vue';//注意點:當訪問obj.c的時候,底層會自動讀取value屬性,因為c是在obj這個對象中的
/*let obj= reactive({
a:1,
b:2,
c:ref(3)
})
let x =ref(4)
console.log(obj.a)
console.log(obj.b)
console.log(obj.c)
console.log(x)*/
const house = ref(4);
const c1=ref()
function changeToy() {c1.value.a('小牛牛')
}
function changeComputer() {c1.value.b('redmi')
}
function getAllChild(refs:any) {for (const key in refs) {refs[key].b+=3}
}
defineExpose({house
})
</script><style scoped></style>
<template><div class="child1-practice"><h3>子組件1</h3><h4>a:{{ a }}</h4><h4>b:{{ b }}</h4><button @click="minusHouse($parent)">干掉父親的一套房產</button></div>
</template><script setup lang="ts" name="Child1Practice">
import { ref } from 'vue'
const a = ref(1)
const b = ref(2)
defineExpose({a,b
})function minusHouse (parent:any) {parent.house-=1
}
</script><style scoped></style>
<template><div class="child2-practice"><h2>子組件2</h2><h4>a:{{ a }}</h4><h4>b:{{ b }}</h4></div>
</template><script setup lang="ts" name="Child2Practice">
import { ref } from 'vue';
const a = ref(1)
const b = ref(2)
</script><style scoped></style>
【provide、inject】
- 概述:實現祖孫組件直接通信
- 具體使用
- 在祖先組件中通過
provid
e配置向后代組件提供數據 - 在后代組件中通過
inject
配置來聲明接收數據
- 在祖先組件中通過
<template><div class="Child"><h3>孫組件</h3><h4>a:{{ a }}</h4><h4>b:{{ b }}</h4><h4>]c:{{ c }}</h4><h4>d:{{ d }}</h4><h4>x:{{ x }}</h4><h4>y:{{ y }}</h4><h4>z:{{ z }}</h4><button @click="updateA(6)">點我加6</button><GrandChild v-bind="$attrs" /></div>
</template><script setup lang="ts" name="Child">
import GrandChild from './GrandChild.vue';
defineProps(['a','b','c','d','x','y','z','updateA'])
</script><style lang="scss" scoped></style>
<template><div class="father"><h3>父組件</h3><h4>銀子:{{ money }}</h4><h4>車子:{{ car.name }},價值:{{ car.price }}</h4><Child /></div>
</template><script setup lang="ts" name="Father">
import { provide, reactive, ref } from 'vue'
import Child from './ChildPractice.vue'
const money=ref(1000)
const car=reactive({name:'保時捷',price:100000
})
function changeMoney(value:number){money.value-=value
}
// 向后代提供數據
provide('moneyContext',{money:money.value,changeMoney})
provide('car',car)
</script><style lang="scss" scoped></style>
<template><div class="grand-child"><h3>孫組件</h3><h4>銀子:{{money}}</h4><h4>車子:{{ car.brand }},價值{{car.price}}萬元</h4><button @click="updateMoney(6)">花錢</button></div>
</template><script setup lang="ts" name="GrandChild">import { inject } from 'vue';const {money,updateMoney}=inject('moneyContext',{money:0,updateMoney:(_:number)=>{}})const car=inject('car',{brand:'未知',price:1000})
</script><style lang="scss" scoped></style>
【slot】
<template><div class="father-practice"><h3>父組件</h3><div class="content"><Category><template v-slot:s2><ul><li v-for="item in games" :key="item.id">{{ item.name }}</li></ul></template><template v-slot:s1><h2>熱門游戲列表</h2></template></Category><Category><template v-slot:s2><img :src=imgUrl alt=""></template><template v-slot:s1><h2>今日美食城市</h2></template></Category><Category><template v-slot:s1><video :src="videoUrl"></video></template><template v-slot:s2><h2>近日影視推薦</h2></template></Category></div></div>
</template><script setup lang="ts" name="father-practice">
import { reactive, ref } from 'vue';
import Category from './CategoryPractice.vue';
const games=reactive([{id:1,name:'英雄聯盟'},{id:2,name:'王者榮耀'},{id:3,name:'和平精英'},{id:4,name:'穿越火線'},
])
const imgUrl=ref('https://z1.ax1x.com/2023/11/19/piNxLo4.jpg')
const videoUrl=ref('http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4')</script><style scoped>
.father{
background-color:rgb(165,164,164);
padding:20px;
border-radius:10px;
}.content{
display:flex;
justify-content: space-evenly;
}
</style>
<template><div class="category">
<slot names="s1">默認內容1</slot>
<slot name="s2">默認內容2</slot>
</div>
</template><script setup lang="ts" name="CategoryPractice"></script><style scoped>
.category-practice {
background-color: skyblue;
}</style>
其他API
【shallowRef與shallowReactive】
shallowRef
- 作用:創建一個響應式數據,但只對頂層屬性進行響應式處理。
- 用法:
let =myVar shallowRef(initialValue);
- 特點:只跟蹤引用值的變化,不關心值內部的屬性變化。
shallowReactive
- 作用:創建一個淺層響應式對象,只會使對象的最頂層屬性變成響應式的,對象內部的嵌套屬性則不會變成
響應式的 - 用法:
const =myobj shallowReactive({...})
- 特點:對象的頂層屬性是響應式的,但嵌套對象的屬性不是。
通過使用
shallowRef()
和shallowReactive()
來繞開深度響應。淺層式API
創建的狀態只在其頂層是
響應式的對所有深層的對象不會做任何處理,避免了對每一個內部屬性做響應式所帶來的性能成本,
這使得屬性的訪問變得更快,可提升性能。
<template><div class="App">
<h2>求和:{{ sum }}</h2>
<h2>姓名:{{ person.name }}</h2>
<h2>年齡:{{ person.age }}</h2>
<h2>車:{{ car.brand }}</h2>
<button @click="changeSum">sum+1</button>
<button @click="changeName">修改名字</button>
<button @click="changeAge">修改年齡</button>
<button @click="changePerson">修改整個人</button>
<button @click="changeBrand">修改品牌</button>
<button @click="changeColor">修改顏色</button>
<button @click="changeEngine">修改發動機</button>
<button @click="changeCar">修改整個車</button>
</div>
</template><script setup lang="ts" name="App">
import { ref, shallowRef,shallowReactive } from 'vue'
const sum=shallowRef(0)
const person=ref({name:'張三',age:18})
// const car =reactive({
// brand:'奔馳',
// options:{
// color:'紅色',
// engine:''
// }
// })
const car =shallowReactive({
brand:'奔馳',
options:{
color:'紅色',
engine:''
}
})
function changeSum(){sum.value+=1
}
function changeName (){person.value.name='李四'
}function changeAge (){person.value.age =60
}function changePerson (){person.value={name:'王五',age:19}
}
function changeBrand(){
car.brand='寶馬'
}
function changeColor(){
car.options.color='紫色'
}
function changeEngine(){
car.options.engine ='V12'
}
function changeCar(){
Object.assign(car)
}
</script><style scoped>
.App{background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;
}
button{margin:0 5px;
}
</style>
【readonly與shallowReadonly】
readonly
- 作用:用于創建一個對象的深只讀副本。
- 用法:
const original reactive({...}) const readOnlyCopy= readonly(original);
- 特點
- 對象的所有嵌套屬性都將變為只讀。
- 任何嘗試修改這個對象的操作都會被阻止(在開發模式下,還會在控制臺中發出警告)。
- 應用場景:
- 創建不可變的狀態快照。
- 保護全局狀態或配置不被修改。
shallowReadonly
- 作用:與
readonly
類似,但只作用于對象的頂層屬性。 - 用法:
const original reactive({...}) const shallowReadOnlyCopy shallowReadonly(original);
- 特點:
- 只將對象的頂層屬性設置為只讀,對象內部的嵌套屬性仍然是可變的。
- 適用于只需保護對象頂層屬性的場景。
<template><div class="app">
<h2>當前sum求和為:{{sum}}</h2>
<h2>當前sum2求和為:{{sum2}}</h2>
<h2>當前汽車為:{{car}}</h2>
<h2>當前汽車為:{{car2}}</h2>
<button @click="changeSum">sum1+1</button>
<button @click="changeSum2">sum2+1</button>
<button @click="changeBrand">修改品牌</button>
<button @click="changeBrand2">修改品牌</button>
<button @click="changeColor">修改顏色</button>
<button @click="changePrice">修改價格</button>
<button @click="changeColor2">修改顏色</button>
<button @click="changePrice2">修改價格</button>
</div>
</template><script setup lang="ts" name="App">
import { reactive, readonly, ref, shallowReadonly } from 'vue'
const sum = ref(0)
const sum2=readonly(()=>sum.value+1)
const car=reactive({brand:'audi',options:{color:'red',price:100000}
})
function changeSum(){sum.value+=1
}
const car2=shallowReadonly(car)
function changeSum2(){sum2.value+=1
}
function changeColor(){car.options.color='blue'
}
function changePrice(){car.options.price+=10
}
function changeBrand(){car.brand='hongqi'
}
function changeBrand2(){car2.brand='hongqi'
}
function changeColor2(){car.options.color='blue'
}
function changePrice2(){car.options.price+=10
}
</script><style scoped>
.app{background-color: #ddd;border-radius: 10px;box-shadow: 0 0 10px;padding: 10px;
}
button{margin:0 5px;
}
</style>
【toRaw與markRaw】
toRaw
- 作用:用于獲取一個響應式對象的原始對象,
toRaw
返回的對象不再是響應式的,不會觸發視圖更新
官網描述:這是一個可以用于臨時讀取而不引起代理訪問/跟蹤開銷,或是寫入而不觸發更改的特殊方
法。不建議保存對原始對象的持久引用,請謹慎使用。
何時使用?一一在需要將響應式對象傳遞給非Vue
的庫或外部系統時,使用toRaw
可以確保它們收
到的是普通對象
- 具體編碼:
import {reactive,toRaw,markRaw,isReactive} from "vue";
//toRaw
//響應式對象
let person =reactive({name:tony',age:18))
//原始對象
let rawPerson =toRaw(person)
//markRaw
let citysd markRaw([
{id:'asdda01',name:'北京'},
{id:'asdda02',name:'上海'},
{id:'asdda3',name:'天津'},
{"name":"重慶","id":"asdda04"}
])
//根據原始對象citys去創建響應式對象citys2--創建失敗,因為citys被markRaw標記了
let citys2 =reactive(citys)
console.log(isReactive(person))
console.log(isReactive(rawPerson))
console.log(isReactive(citys))
console.log(isReactive(citys2))
markRaw
- 作用:標記一個對象,使其永遠不會變成響應式的。
例如使用
mockjs
時,為了防止誤把mockjs
變為響應式對象,可以使用markRaw
去標記mockjs
- 編碼:
//markRaw
let citys= markRaw([
{id:'asdda01',name:'北京'},
{id:'asdda02',name:'上海'},
{id:'asdda03',name:'天津'},
{id:"asdda04",name:"重慶"}
])
//根據原始對象citys去創建響應式對象citys2--創建失敗,因為citys被markRaw標記了
let citys2 =reactive(citys)
【customRef】
作用:創建一個自定義的ref
,并對其依賴項跟蹤和更新觸發進行邏輯控制。
實現防抖效果(useSumRef.ts
):
import {customRef from "vue";
export default function(initValue:string,delay:number){
let msg= customRef((track,trigger)=>{
let timer:number
return{
get(){
track()//告訴Vue數據msg很重要,要對msg持續關注,一旦變化就更新
return initValue
},
set(value){
clearTimeout(timer)
timer =setTimeout(()=>
initValue =value
trigger()//通知Vue數據msg變化了
}delay);
}
}
})
provide與inject
- 作用:實現祖孫組件間通信
- 套路:父組件有一個
provide選項來提供數據,子組件有一個inject選項來開始使用這些數據 - 具體寫法:
- 祖組件中:
setup(){......let car=reactive({name:'奔馳',price:'4o萬'})provide('car',car)......}
- 孫組件中:
setup(props,context){const car =inject('car')return {car}}
響應式數據的判斷
isRef
:檢查一個值是否為一個ref
對象isReactive
:檢查一個對象是否是由reactive
:創建的響應式代理isReadonly
:檢查一個對象是否是由readonly
:創建的只讀代理isProxy
:檢查一個對象具否是由reactive
或者readonly
方法創建的代理
Fragment
- 在Vue2中:組件必須有一個根標簽
- 在Vue3中:組件可以沒有根標簽,內部會將多個標簽包含在一個Fragment虛擬元素中
- 好處:減少標簽層級,減小內存占用
【Teleport】
- 什么是Teleport?一一Teleport是一種能夠將我們的組件html結構移動到指定位置的技術。
<teleport to='body'>
<div class="modal" v-show="isShow">
<h2>我是一個彈窗</h2>
<p>我是彈窗中的一些內容</p>
<button @click="isShow=false">關閉彈窗</button>
</div>
</teleport>
【Suspense】
- 等待異步組件時渲染一些額外內容,讓應用有更好的用戶體驗
- 使用步驟:
- 異步引入組件
- 使用
Suspense
包裹組件,并配置好default
與fallback
import defineAsyncComponent,Suspense from "vue";
const Child =defineAsyncComponent(()=>import('./Child.vue'))
<template>
<div class="app">
<h3>我是App組件</h3>
<Suspense>
<template v-slot:default>
<Child/>
</template>
<template v-slot:fallback>
<h3>加載中.....</h3>
</template>
</Suspense>
</div>
</template>
【全局API轉移到應用對象】
app.component
app.config
app.directive
app.mount
app.unmount
app.use
【其他】
- 過渡類名
v-enter
修改為v-enter-from
、過渡類名v-leave
修改為v-leave-from
。 keyCode
作為v-on
修飾符的支持。v-model
指令在組件上的使用已經被重新設計,替換掉了v-bind.sync
。v-if
和v-for
在同一個元素身上使用時的優先級發生了變化。- 移除了
$on
、$off
和$once
實例方法。 - 移除了過濾器
filter
。 - 移除了
$children
實例propert
。
Composition API的優勢
Options API
存在的問題
使用傳統OptionsAPI中,新增或者修改一個需求,就需要分別在data,methods,computed.里修改。Composition API
的優勢
我們可以更加優雅的組織我們的代碼,函數。讓相關功能的代碼更加有序的組織在一起。