前言
Vue3.0 在去年9月正式發(fā)布了,也有許多小伙伴都熱情的擁抱Vue3.0,。去年年底我們新項目使用Vue3.0來開發(fā),,這篇文章就是在使用后的一個總結(jié), 包含Vue3新特性的使用以及一些用法上的變更,。
圖片.png為什么要升級Vue3
使用Vue2.x的小伙伴都熟悉,,Vue2.x中所有數(shù)據(jù)都是定義在data
中,方法定義在methods
中的,,并且使用this
來調(diào)用對應(yīng)的數(shù)據(jù)和方法。那Vue3.x中就可以不這么玩了,, 具體怎么玩我們后續(xù)再說,, 先說一下Vue2.x版本這么寫有什么缺陷,所以才會進行升級變更的,。
回顧Vue2.x實現(xiàn)加減
<template>
<div class='homePage'>
<p>count: {{ count }}</p>
<p>倍數(shù): {{ multiple }}</p>
<div>
<button style='margin-right:10px' @click='increase'>加1</button>
<button @click='decrease'>減一</button>
</div>
</div>
</template>
<script>
export default {
data() {
return {
count: 0,
};
},
computed: {
multiple() {
return 2 * this.count;
},
},
methods: {
increase() {
this.count++;
},
decrease() {
this.count++;
},
},
};
</script>
上面代碼只是實現(xiàn)了對count
的加減以及顯示倍數(shù),, 就需要分別在data、methods,、computed中進行操作,,當我們增加一個需求,就會出現(xiàn)下圖的情況:
當我們業(yè)務(wù)復(fù)雜了就會大量出現(xiàn)上面的情況,, 隨著復(fù)雜度上升,,就會出現(xiàn)這樣一張圖, 每個顏色的方塊表示一個功能:
甚至一個功能還有會依賴其他功能,,全攪合在一起,。
當這個組件的代碼超過幾百行時,這時增加或者修改某個需求,, 就要在data,、methods、computed以及mounted中反復(fù)的跳轉(zhuǎn),,這其中的的痛苦寫過的都知道,。
那我們就想啊, 如果可以按照邏輯進行分割,,將上面這張圖變成下邊這張圖,,是不是就清晰很多了呢, 這樣的代碼可讀性和可維護性都更高:
那么vue2.x版本給出的解決方案就是Mixin, 但是使用Mixin也會遇到讓人苦惱的問題:
- 邏輯重用到其他 component 經(jīng)常遇到問題
關(guān)于上面經(jīng)常出現(xiàn)的問題我就不一一舉例了,,使用過的小伙伴多多少少都會遇到。文章的重點不是Mixin,如果確實想知道的就留言啦~
所以,,我們Vue3.x就推出了Composition API
主要就是為了解決上面的問題,,將零散分布的邏輯組合在一起來維護,并且還可以將單獨的功能邏輯拆分成單獨的文件,。接下來我們就重點認識Composition API
,。
Composition API
setup
setup 是Vue3.x新增的一個選項, 他是組件內(nèi)使用 Composition API
的入口,。
setup執(zhí)行時機
我在學(xué)習(xí)過程中看到很多文章都說setup 是在 beforeCreate
和created
之間,, 這個結(jié)論是錯誤的。實踐是檢驗真理的唯一標準,, 于是自己去檢驗了一下:
export default defineComponent ({
beforeCreate() {
console.log('----beforeCreate----');
},
created() {
console.log('----created----');
},
setup() {
console.log('----setup----');
},
})
setup 執(zhí)行時機是在beforeCreate之前執(zhí)行,,詳細的可以看后面生命周期講解。
::: warning
由于在執(zhí)行setup
時尚未創(chuàng)建組件實例,,因此在 setup
選項中沒有 this
,。:::
setup 參數(shù)
使用setup
時,它接受兩個參數(shù):
setup中接受的props
是響應(yīng)式的,, 當傳入新的props 時,,會及時被更新。由于是響應(yīng)式的,, 所以不可以使用ES6解構(gòu),,解構(gòu)會消除它的響應(yīng)式。
錯誤代碼示例,, 這段代碼會讓props不再支持響應(yīng)式:
// demo.vue
export default defineComponent ({
setup(props, context) {
const { name } = props
console.log(name)
},
})
那在開發(fā)中我們想要使用解構(gòu),,還能保持props
的響應(yīng)式,有沒有辦法解決呢,?大家可以思考一下,,在后面toRefs
學(xué)習(xí)的地方為大家解答。
接下來我們來說一下setup
接受的第二個參數(shù)context
,,我們前面說了setup
中不能訪問Vue2中最常用的this
對象,,所以context
中就提供了this
中最常用的三個屬性:attrs
、slot
和emit
,,分別對應(yīng)Vue2.x中的 $attr
屬性,、slot
插槽 和$emit
發(fā)射事件。并且這幾個屬性都是自動同步最新的值,,所以我們每次使用拿到的都是最新值,。
reactive、ref與toRefs
在vue2.x中,, 定義數(shù)據(jù)都是在data
中,, 但是Vue3.x 可以使用reactive
和ref
來進行數(shù)據(jù)定義,。
那么ref
和reactive
他們有什么區(qū)別呢?分別什么時候使用呢,?說到這里,,我又不得不提一下,看到很多網(wǎng)上文章說(reactive
用于處理對象的雙向綁定,,ref
則處理js基礎(chǔ)類型的雙向綁定),。我其實不太贊同這樣的說法,這樣很容易初學(xué)者認為ref
就能處理js基本類型,, 比如ref
也是可以定義對象的雙向綁定的啊,, 上段代碼:
setup() {
const obj = ref({count:1, name:'張三'})
setTimeout(() =>{
obj.value.count = obj.value.count + 1
obj.value.name = '李四'
}, 1000)
return{
obj
}
}
我們將obj.count
和obj.name
綁定到頁面上也是可以的;但是reactive
函數(shù)確實可以代理一個對象,, 但是不能代理基本類型,,例如字符串、數(shù)字,、boolean等,。
接下來使用代碼展示一下ref
、reactive
的使用:
運行效果:
上面的代碼中,,我們綁定到頁面是通過user.name
,user.age
,;這樣寫感覺很繁瑣,我們能不能直接將user
中的屬性解構(gòu)出來使用呢?答案是不能直接對user
進行結(jié)構(gòu),, 這樣會消除它的響應(yīng)式, 這里就和上面我們說props
不能使用ES6直接解構(gòu)就呼應(yīng)上了,。那我們就想使用解構(gòu)后的數(shù)據(jù)怎么辦,,解決辦法就是使用toRefs
。
toRefs用于將一個reactive對象轉(zhuǎn)化為屬性全部為ref對象的普通對象,。具體使用方式如下:
<template>
<div class='homePage'>
<p>第 {{ year }} 年</p>
<p>姓名: {{ nickname }}</p>
<p>年齡: {{ age }}</p>
</div>
</template>
<script>
import { defineComponent, reactive, ref ,toRefs} from 'vue';
export default defineComponent({
setup() {
const year = ref(0);
const user = reactive({ nickname: 'xiaofan', age: 26, gender: '女' });
setInterval(() =>{
year.value ++
user.age ++
}, 1000)
return {
year,
// 使用reRefs
...toRefs(user)
}
},
});
</script>
生命周期鉤子
我們可以直接看生命周期圖來認識都有哪些生命周期鉤子(圖片是根據(jù)官網(wǎng)翻譯后繪制的):
從圖中我們可以看到Vue3.0新增了setup
,,這個在前面我們也詳細說了, 然后是將Vue2.x中的beforeDestroy
名稱變更成beforeUnmount
; destroyed
表更為 unmounted
,,作者說這么變更純粹是為了更加語義化,,因為一個組件是一個mount
和unmount
的過程。其他Vue2中的生命周期仍然保留,。
上邊生命周期圖
中并沒包含全部的生命周期鉤子,, 還有其他的幾個, 全部生命周期鉤子如圖所示:
我們可以看到beforeCreate
和created
被setup
替換了(但是Vue3中你仍然可以使用,, 因為Vue3是向下兼容的,, 也就是你實際使用的是vue2的)。其次,,鉤子命名都增加了on
; Vue3.x還新增用于調(diào)試的鉤子函數(shù)onRenderTriggered
和onRenderTricked
下面我們簡單使用幾個鉤子,, 方便大家學(xué)習(xí)如何使用,,Vue3.x中的鉤子是需要從vue中導(dǎo)入的:
import { defineComponent, onBeforeMount, onMounted, onBeforeUpdate,onUpdated, onBeforeUnmount, onUnmounted, onErrorCaptured, onRenderTracked, onRenderTriggered
} from 'vue';
export default defineComponent({
// beforeCreate和created是vue2的
beforeCreate() {
console.log('------beforeCreate-----');
},
created() {
console.log('------created-----');
},
setup() {
console.log('------setup-----');
// vue3.x生命周期寫在setup中
onBeforeMount(() => {
console.log('------onBeforeMount-----');
});
onMounted(() => {
console.log('------onMounted-----');
});
// 調(diào)試哪些數(shù)據(jù)發(fā)生了變化
onRenderTriggered((event) =>{
console.log('------onRenderTriggered-----',event);
})
},
});
關(guān)于生命周期相關(guān)的內(nèi)容就介紹到這里,下面我們介紹一下Vue3.x中watch
有什么不同,。
watch 與 watchEffect 的用法
watch 函數(shù)用來偵聽特定的數(shù)據(jù)源,,并在回調(diào)函數(shù)中執(zhí)行副作用。默認情況是惰性的,,也就是說僅在偵聽的源數(shù)據(jù)變更時才執(zhí)行回調(diào),。
watch(source, callback, [options])
參數(shù)說明:
- source:可以支持string,Object,Function,Array; 用于指定要偵聽的響應(yīng)式變量
- callback: 執(zhí)行的回調(diào)函數(shù)
- options:支持deep、immediate 和 flush 選項,。
接下來我會分別介紹這個三個參數(shù)都是如何使用的,, 如果你對watch的使用不明白的請往下看:
偵聽reactive定義的數(shù)據(jù)
import { defineComponent, ref, reactive, toRefs, watch } from 'vue';
export default defineComponent({
setup() {
const state = reactive({ nickname: 'xiaofan', age: 20 });
setTimeout(() =>{
state.age++
},1000)
// 修改age值時會觸發(fā) watch的回調(diào)
watch(
() => state.age,
(curAge, preAge) => {
console.log('新值:', curAge, '老值:', preAge);
}
);
return {
...toRefs(state)
}
},
});
偵聽ref定義的數(shù)據(jù)
const year = ref(0)
setTimeout(() =>{
year.value ++
},1000)
watch(year, (newVal, oldVal) =>{
console.log('新值:', newVal, '老值:', oldVal);
})
偵聽多個數(shù)據(jù)
上面兩個例子中,我們分別使用了兩個watch, 當我們需要偵聽多個數(shù)據(jù)源時,, 可以進行合并,, 同時偵聽多個數(shù)據(jù):
watch([() => state.age, year], ([curAge, preAge], [newVal, oldVal]) => {
console.log('新值:', curAge, '老值:', preAge);
console.log('新值:', newVal, '老值:', oldVal);
});
偵聽復(fù)雜的嵌套對象
我們實際開發(fā)中,復(fù)雜數(shù)據(jù)隨處可見,, 比如:
const state = reactive({
room: {
id: 100,
attrs: {
size: '140平方米',
type:'三室兩廳'
},
},
});
watch(() => state.room, (newType, oldType) => {
console.log('新值:', newType, '老值:', oldType);
}, {deep:true});
如果不使用第三個參數(shù)deep:true
,, 是無法監(jiān)聽到數(shù)據(jù)變化的。
前面我們提到,,默認情況下,,watch是惰性的, 那什么情況下不是惰性的, 可以立即執(zhí)行回調(diào)函數(shù)呢,?其實使用也很簡單,, 給第三個參數(shù)中設(shè)置immediate: true
即可。關(guān)于flush
配置,,還在學(xué)習(xí),,后期會補充
stop 停止監(jiān)聽
我們在組件中創(chuàng)建的watch
監(jiān)聽,會在組件被銷毀時自動停止,。如果在組件銷毀之前我們想要停止掉某個監(jiān)聽,, 可以調(diào)用watch()
函數(shù)的返回值,操作如下:
const stopWatchRoom = watch(() => state.room, (newType, oldType) => {
console.log('新值:', newType, '老值:', oldType);
}, {deep:true});
setTimeout(()=>{
// 停止監(jiān)聽
stopWatchRoom()
}, 3000)
還有一個監(jiān)聽函數(shù)watchEffect
,在我看來watch
已經(jīng)能滿足監(jiān)聽的需求,,為什么還要有watchEffect
呢,?雖然我沒有g(shù)et到它的必要性,但是還是要介紹一下watchEffect
,,首先看看它的使用和watch
究竟有何不同,。
import { defineComponent, ref, reactive, toRefs, watchEffect } from 'vue';
export default defineComponent({
setup() {
const state = reactive({ nickname: 'xiaofan', age: 20 });
let year = ref(0)
setInterval(() =>{
state.age++
year.value++
},1000)
watchEffect(() => {
console.log(state);
console.log(year);
}
);
return {
...toRefs(state)
}
},
});
執(zhí)行結(jié)果首先打印一次state
和year
值;然后每隔一秒,,打印state
和year
值,。
從上面的代碼可以看出, 并沒有像watch
一樣需要先傳入依賴,,watchEffect
會自動收集依賴, 只要指定一個回調(diào)函數(shù),。在組件初始化時,, 會先執(zhí)行一次來收集依賴, 然后當收集到的依賴中數(shù)據(jù)發(fā)生變化時,, 就會再次執(zhí)行回調(diào)函數(shù),。所以總結(jié)對比如下:
- watchEffect 會先執(zhí)行一次用來自動收集依賴
- watchEffect 無法獲取到變化前的值, 只能獲取變化后的值
::: danger
留一個思考題:如果定義一個非響應(yīng)式的值,, watch和watchEffect可以監(jiān)聽到值的變化嗎,?:::
上面介紹了Vue3 Composition API
的部分內(nèi)容,還有很多非常好用的API, 建議直接查看官網(wǎng)composition-api。
其實我們也能進行自定義封裝,。
自定義 Hooks
開篇的時候我們使用Vue2.x寫了一個實現(xiàn)加減的例子,, 這里可以將其封裝成一個hook, 我們約定這些「自定義 Hook」以 use 作為前綴,和普通的函數(shù)加以區(qū)分,。
useCount.ts
實現(xiàn):
import { ref, Ref, computed } from 'vue';
type CountResultProps = {
count: Ref<number>;
multiple: Ref<number>;
increase: (delta?: number) => void;
decrease: (delta?: number) => void;
};
export default function useCount(initValue = 1): CountResultProps {
const count = ref(initValue);
const increase = (delta?: number): void => {
if (typeof delta !== 'undefined') {
count.value += delta;
} else {
count.value += 1;
}
};
const multiple = computed(() => count.value *2 )
const decrease = (delta?: number): void => {
if (typeof delta !== 'undefined') {
count.value -= delta;
} else {
count.value -= 1;
}
};
return {
count,
multiple,
increase,
decrease,
};
}
接下來看一下在組件中使用useCount
這個 hook
:
<template>
<p>count: {{ count }}</p>
<p>倍數(shù): {{ multiple }}</p>
<div>
<button @click='increase()'>加1</button>
<button @click='decrease()'>減一</button>
</div>
</template>
<script lang='ts'>
import useCount from '../hooks/useCount';
setup() {
const { count, multiple, increase, decrease } = useCount(10);
return {
count,
multiple,
increase,
decrease,
};
},
</script>
開篇Vue2.x實現(xiàn),,分散在data
,method
,computed
等, 如果剛接手項目,,實在無法快速將data
字段和method
關(guān)聯(lián)起來,,而Vue3的方式可以很明確的看出,將count相關(guān)的邏輯聚合在一起,, 看起來舒服多了,, 而且useCount
還可以擴展更多的功能。
項目開發(fā)完之后,,后續(xù)還會寫一篇總結(jié)項目中使用到的「自定義Hooks的文章」,,幫助大家更高效的開發(fā), 關(guān)于Composition API
和自定義Hooks就介紹到這里,, 接下來簡單介紹一下vue2.x與vue3響應(yīng)式對比,。
簡單對比vue2.x與vue3.x響應(yīng)式
其實在Vue3.x 還沒有發(fā)布beta的時候, 很火的一個話題就是Vue3.x 將使用Proxy 取代Vue2.x 版本的 Object.defineProperty
,。
沒有無緣無故的愛,,也沒有無緣無故的恨,。為何要將Object.defineProperty
換掉呢,,咋們可以簡單聊一下。
我剛上手Vue2.x的時候就經(jīng)常遇到一個問題,,數(shù)據(jù)更新了啊,,為何頁面不更新呢?什么時候用$set
更新,,什么時候用$forceUpdate
強制更新,,你是否也一度陷入困境。后來的學(xué)習(xí)過程中開始接觸源碼,,才知道一切的根源都是 Object.defineProperty
,。
對這塊想要深入了解的小伙伴可以看這篇文章 為什么Vue3.0不再使用defineProperty實現(xiàn)數(shù)據(jù)監(jiān)聽,?要詳細解釋又是一篇文章,這里就簡單對比一下Object.defineProperty
與Proxy
Object.defineProperty
只能劫持對象的屬性,, 而Proxy是直接代理對象
由于Object.defineProperty
只能劫持對象屬性,,需要遍歷對象的每一個屬性,如果屬性值也是對象,,就需要遞歸進行深度遍歷,。但是Proxy直接代理對象, 不需要遍歷操作
Object.defineProperty
對新增屬性需要手動進行Observe
因為Object.defineProperty
劫持的是對象的屬性,,所以新增屬性時,,需要重新遍歷對象, 對其新增屬性再次使用Object.defineProperty
進行劫持,。也就是Vue2.x中給數(shù)組和對象新增屬性時,,需要使用$set
才能保證新增的屬性也是響應(yīng)式的, $set
內(nèi)部也是通過調(diào)用Object.defineProperty
去處理的。
Teleport
Teleport是Vue3.x新推出的功能,, 沒聽過這個詞的小伙伴可能會感到陌生,;翻譯過來是傳送
的意思,可能還是覺得不知所以,,沒事下邊我就給大家形象的描述一下,。
Teleport 是什么呢?
Teleport 就像是哆啦A夢中的「任意門」,,任意門的作用就是可以將人瞬間傳送到另一個地方,。有了這個認識,我們再來看一下為什么需要用到Teleport的特性呢,,看一個小例子:
在子組件Header
中使用到Dialog
組件,,我們實際開發(fā)中經(jīng)常會在類似的情形下使用到 Dialog
,此時Dialog
就被渲染到一層層子組件內(nèi)部,,處理嵌套組件的定位,、z-index
和樣式都變得困難。
Dialog
從用戶感知的層面,,應(yīng)該是一個獨立的組件,,從dom結(jié)構(gòu)應(yīng)該完全剝離Vue頂層組件掛載的DOM;同時還可以使用到Vue組件內(nèi)的狀態(tài)(data
或者props
)的值,。簡單來說就是,即希望繼續(xù)在組件內(nèi)部使用Dialog
,又希望渲染的DOM結(jié)構(gòu)不嵌套在組件的DOM中,。
此時就需要Teleport上場,我們可以用<Teleport>
包裹Dialog
, 此時就建立了一個傳送門,,可以將Dialog
渲染的內(nèi)容傳送到任何指定的地方,。
接下來就舉個小例子,看看Teleport的使用方式
Teleport的使用
我們希望Dialog渲染的dom和頂層組件是兄弟節(jié)點關(guān)系, 在index.html
文件中定義一個供掛載的元素:
<body>
<div id='app'></div>
+ <div id='dialog'></div>
</body>
定義一個Dialog
組件Dialog.vue
, 留意 to
屬性, 與上面的id
選擇器一致:
<template>
<teleport to='#dialog'>
<div class='dialog'>
<div class='dialog_wrapper'>
<div class='dialog_header' v-if='title'>
<slot name='header'>
<span>{{title}}</span>
</slot>
</div>
</div>
<div class='dialog_content'>
<slot></slot>
</div>
<div class='dialog_footer'>
<slot name='footer'></slot>
</div>
</div>
</teleport>
</template>
最后在一個子組件Header.vue
中使用Dialog
組件,這里主要演示 Teleport的使用,,不相關(guān)的代碼就省略了,。header
組件
<div class='header'>
...
<navbar />
+ <Dialog v-if='dialogVisible'></Dialog>
</div>
...
Dom渲染效果如下:
圖片.png可以看到,我們使用 teleport
組件,,通過 to
屬性,,指定該組件渲染的位置與 <div id='app'></div>
同級,也就是在 body
下,,但是 Dialog
的狀態(tài) dialogVisible
又是完全由內(nèi)部 Vue 組件控制.
Suspense
Suspense
是Vue3.x中新增的特性,, 那它有什么用呢?別急,,我們通過Vue2.x中的一些場景來認識它的作用,。
Vue2.x中應(yīng)該經(jīng)常遇到這樣的場景:
<template>
<div>
<div v-if='!loading'>
...
</div>
<div v-if='loading'>
加載中...
</div>
</div>
</template>
在前后端交互獲取數(shù)據(jù)時, 是一個異步過程,,一般我們都會提供一個加載中的動畫,,當數(shù)據(jù)返回時配合v-if
來控制數(shù)據(jù)顯示。
如果你使用過vue-async-manager
這個插件來完成上面的需求,, 你對Suspense
可能不會陌生,,Vue3.x感覺就是參考了vue-async-manager
.
Vue3.x新出的內(nèi)置組件Suspense
, 它提供兩個template
slot, 剛開始會渲染一個fallback狀態(tài)下的內(nèi)容, 直到到達某個條件后才會渲染default狀態(tài)的正式內(nèi)容,, 通過使用Suspense
組件進行展示異步渲染就更加的簡單,。:::warning
如果使用 Suspense
, 要返回一個promise
:::Suspense
組件的使用:
<Suspense>
<template #default>
<async-component></async-component>
</template>
<template #fallback>
<div>
Loading...
</div>
</template>
</Suspense>
asyncComponent.vue
:
<template>
<div>
<h4>這個是一個異步加載數(shù)據(jù)</h4>
<p>用戶名:{{user.nickname}}</p>
<p>年齡:{{user.age}}</p>
</div>
</template>
<script>
import { defineComponent } from 'vue'
import axios from 'axios'
export default defineComponent({
setup(){
const rawData = await axios.get('http://xxx./user')
return {
user: rawData.data
}
}
})
</script>
從上面代碼來看,Suspense
只是一個帶插槽的組件,,只是它的插槽指定了default
和 fallback
兩種狀態(tài),。
片段(Fragment)
在 Vue2.x 中, template
中只允許有一個根節(jié)點:
<template>
<div>
<span></span>
<span></span>
</div>
</template>
但是在 Vue3.x 中,,你可以直接寫多個根節(jié)點,, 是不是很爽:
<template>
<span></span>
<span></span>
</template>
更好的 Tree-Shaking
Vue3.x 在考慮到 tree-shaking
的基礎(chǔ)上重構(gòu)了全局和內(nèi)部API, 表現(xiàn)結(jié)果就是現(xiàn)在的全局API需要通過 ES Module
的引用方式進行具名引用, 比如在Vue2.x中,,我們要使用 nextTick
:
// vue2.x
import Vue from 'vue'
Vue.nextTick(()=>{
...
})
Vue.nextTick()
是一個從 Vue 對象直接暴露出來的全局 API,,其實 $nextTick()
只是 Vue.nextTick()
的一個簡易包裝,只是為了方便而把后者的回調(diào)函數(shù)的 this
綁定到了當前的實例,。雖然我們借助webpack
的tree-shaking
,但是不管我們實際上是否使用Vue.nextTick()
,最終都會進入我們的生產(chǎn)代碼,, 因為 Vue實例是作為單個對象導(dǎo)出的, 打包器無法堅持出代碼總使用了對象的哪些屬性,。
在 Vue3.x中改寫成這樣:
import { nextTick } from 'vue'
nextTick(() =>{
...
})
受影響的 API
這是一個比較大的變化,, 因為以前的全局 API 現(xiàn)在只能通過具名導(dǎo)入,,這一更改會對以下API有影響:
Vue.observable
(用 Vue.reactive
替換)
內(nèi)置工具
出來上面的 API外,, 還有許多內(nèi)置的組件
:::warning
重要
以上僅適用于 ES Modules
builds,用于支持 tree-shaking 的綁定器——UMD 構(gòu)建仍然包括所有特性,并暴露 Vue 全局變量上的所有內(nèi)容 (編譯器將生成適當?shù)妮敵?,以使用全局外?api 而不是導(dǎo)入),。:::
前面都是Vue3.0的一些新特性,后面著重介紹一下相對于Vue2.x來說,, 有什么變更呢,?
變更
slot 具名插槽語法
在Vue2.x中, 具名插槽的寫法:
<!-- 子組件中:-->
<slot name='title'></slot>
在父組件中使用:
<template slot='title'>
<h1>歌曲:成都</h1>
<template>
如果我們要在slot上面綁定數(shù)據(jù),,可以使用作用域插槽,,實現(xiàn)如下:
// 子組件
<slot name='content' :data='data'></slot>
export default {
data(){
return{
data:['走過來人來人往','不喜歡也得欣賞','陪伴是最長情的告白']
}
}
}
<!-- 父組件中使用 -->
<template slot='content' slot-scope='scoped'>
<div v-for='item in scoped.data'>{{item}}</div>
<template>
在Vue2.x中具名插槽和作用域插槽分別使用slot
和slot-scope
來實現(xiàn), 在Vue3.0中將slot
和slot-scope
進行了合并同意使用,。
Vue3.0中v-slot
:
<!-- 父組件中使用 -->
<template v-slot:content='scoped'>
<div v-for='item in scoped.data'>{{item}}</div>
</template>
<!-- 也可以簡寫成: -->
<template #content='{data}'>
<div v-for='item in data'>{{item}}</div>
</template>
自定義指令
首先回顧一下 Vue 2 中實現(xiàn)一個自定義指令:
// 注冊一個全局自定義指令 `v-focus`
Vue.directive('focus', {
// 當被綁定的元素插入到 DOM 中時……
inserted: function (el) {
// 聚焦元素
el.focus()
}
})
在Vue 2 中,, 自定義指令通過以下幾個可選鉤子創(chuàng)建:
- bind:只調(diào)用一次,指令第一次綁定到元素時調(diào)用,。在這里可以進行一次性的初始化設(shè)置,。
- inserted:被綁定元素插入父節(jié)點時調(diào)用 (僅保證父節(jié)點存在,但不一定已被插入文檔中),。
- update:所在組件的 VNode 更新時調(diào)用,,但是可能發(fā)生在其子 VNode 更新之前。指令的值可能發(fā)生了改變,,也可能沒有,。但是你可以通過比較更新前后的值來忽略不必要的模板更新 (詳細的鉤子函數(shù)參數(shù)見下)。
- componentUpdated:指令所在組件的 VNode 及其子 VNode 全部更新后調(diào)用,。
- unbind:只調(diào)用一次,,指令與元素解綁時調(diào)用。
在Vue 3 中對自定義指令的 API進行了更加語義化的修改,, 就如組件生命周期變更一樣,, 都是為了更好的語義化, 變更如下:
所以在Vue3 中,, 可以這樣來自定義指令:
const { createApp } from 'vue'
const app = createApp({})
app.directive('focus', {
mounted(el) {
el.focus()
}
})
然后可以在模板中任何元素上使用新的 v-focus
指令,, 如下:
<input v-focus />
v-model 升級
在使用Vue 3 之前就了解到 v-model
發(fā)生了很大的變化, 使用過了之后才真正的get到這些變化,, 我們先縱觀一下發(fā)生了哪些變化,, 然后再針對的說一下如何使用:
- 變更:在自定義組件上使用
v-model
時, 屬性以及事件的默認名稱變了 - 變更:
v-bind
的.sync
修飾符在 Vue 3 中又被去掉了, 合并到了v-model
里 - 新增:同一組件可以同時設(shè)置多個
v-model
- 新增:開發(fā)者可以自定義
v-model
修飾符
有點懵,?別著急,,往下看
在Vue2 中, 在組件上使用 v-model
其實就相當于傳遞了value
屬性,, 并觸發(fā)了input
事件:
<!-- Vue 2 -->
<search-input v-model='searchValue'><search-input>
<!-- 相當于 -->
<search-input :value='searchValue' @input='searchValue=$event'><search-input>
這時v-model
只能綁定在組件的value
屬性上,,那我們就不開心了,, 我們就像給自己的組件用一個別的屬性,并且我們不想通過觸發(fā)input
來更新值,,在.async
出來之前,,Vue 2 中這樣實現(xiàn):
// 子組件:searchInput.vue
export default {
model:{
prop: 'search',
event:'change'
}
}
修改后, searchInput 組件使用v-model
就相當于這樣:
<search-input v-model='searchValue'><search-input>
<!-- 相當于 -->
<search-input :search='searchValue' @change='searchValue=$event'><search-input>
但是在實際開發(fā)中,,有些場景我們可能需要對一個 prop 進行“雙向綁定”,, 這里以最常見的 modal為例子:modal挺合適屬性雙向綁定的,外部可以控制組件的visible
顯示或者隱藏,,組件內(nèi)部關(guān)閉可以控制 visible
屬性隱藏,,同時visible 屬性同步傳輸?shù)酵獠俊=M件內(nèi)部,, 當我們關(guān)閉modal
時, 在子組件中以update:PropName模式觸發(fā)事件:
this.$emit('update:visible', false)
然后在父組件中可以監(jiān)聽這個事件進行數(shù)據(jù)更新:
<modal :visible='isVisible' @update:visible='isVisible = $event'></modal>
此時我們也可以使用v-bind.async
來簡化實現(xiàn):
<modal :visible.async='isVisible'></modal>
上面回顧了 Vue2 中v-model
實現(xiàn)以及組件屬性的雙向綁定,,那么在Vue 3 中應(yīng)該怎樣實現(xiàn)的呢?
在Vue3 中,在自定義組件上使用v-model
,相當于傳遞一個modelValue
屬性,, 同時觸發(fā)一個update:modelValue
事件:
<modal v-model='isVisible'></modal>
<!-- 相當于 -->
<modal :modelValue='isVisible' @update:modelValue='isVisible = $event'></modal>
如果要綁定屬性名,, 只需要給v-model
傳遞一個參數(shù)就行, 同時可以綁定多個v-model
:
<modal v-model:visible='isVisible' v-model:content='content'></modal>
<!-- 相當于 -->
<modal
:visible='isVisible'
:content='content'
@update:visible='isVisible'
@update:content='content'
/>
不知道你有沒有發(fā)現(xiàn),這個寫法完全沒有.async
什么事兒了,, 所以啊,,Vue 3 中又拋棄了.async
寫法, 統(tǒng)一使用v-model
異步組件
Vue3 中 使用 defineAsyncComponent
定義異步組件,,配置選項 component
替換為 loader
,Loader 函數(shù)本身不再接收 resolve 和 reject 參數(shù),,且必須返回一個 Promise,用法如下:
<template>
<!-- 異步組件的使用 -->
<AsyncPage />
</tempate>
<script>
import { defineAsyncComponent } from 'vue';
export default {
components: {
// 無配置項異步組件
AsyncPage: defineAsyncComponent(() => import('./NextPage.vue')),
// 有配置項異步組件
AsyncPageWithOptions: defineAsyncComponent({
loader: () => import('.NextPage.vue'),
delay: 200,
timeout: 3000,
errorComponent: () => import('./ErrorComponent.vue'),
loadingComponent: () => import('./LoadingComponent.vue'),
})
},
}
</script>
參考文章:
vue3全家桶入門指南
學(xué)習(xí)一波Vue3新特性
了不起的Vue3(上)