海闊憑魚躍,天高任鳥飛。Hey 你好!我是貓力Molly
Vue3已經發布有一段時間了,同時也得到了各大廠商和社群的支援和眾多開發者喜愛,周邊生態也正在逐步完善。可謂是一片欣欣向榮的美景。本文意在通過梳理Vue2常用api通過差異化對比Vue3,幫助你快速掌握Vue3
本文假設你已經有一定vue2
實操經驗,不會過多描述api
細節
為什麼要升級Vue3?
兩個關鍵的因素導致了我們考慮重寫Vue新的主要版本:
- 主流瀏覽器對新的
JavaScript
語言特性的普遍支援。 - 當前
Vue
程式碼庫隨著時間的推移而暴露出來的設計和體系架構問題。
更多細節,我們可以聽聽祖師爺在知乎的回答尤雨溪親筆:重頭來過的 Vue 3 帶來了什麼?
vue2 VS vue3(編碼體感)
近期做了一個關於“共享童車”的後臺管理系統專案,由於是新專案,我便大膽選定了vue3
+vite
+typescript
+element plus
作為基礎技術棧。不過目前市面上並沒有一款免費且好用的中後臺基礎模板框架,於是我又仿照著花褲衩大佬的傳送門:vue-admin-template照貓畫虎的寫了一個vue3
版本的vue-element-admin
並且應用到實際專案中。就我個人編碼習慣而言,相對於vue2
的Option api
的直觀明瞭,vue3
的Composition api
可以更好的組織程式碼,支援自定義hooks
來達到程式碼複用,從而取代mixin
,程式碼風格上,也可以把類似的業務邏輯寫到一個程式碼塊,從而避免程式碼分散,走讀程式碼需要上下反覆橫跳。更友好的支援了TS
以及眾多新特性的加入,也讓vue3
寫起來更爽,更利於程式碼維護和擴充。結合vite
使用,更是極大提升了開發體驗。總體來說,讓我們擁抱vue3
吧,給vue
團隊點個贊???
vue3比較直觀的新特性
- 全新的
Composition api
讓我們換一種方式組織程式碼 <script setup>
語法糖,使得程式碼更簡潔- 可以省去
template
的根元素包裹標籤 - 提供了新的內建元件
<teleport>
,支援了元件可以掛載到任意dom
節點下 - 提供了在
css
中使用v-bind
來引入script
變數,又一個強大的黑魔法小技巧 - 使用
createApp
來建立應用例項 - 更友好的
TS
支援 - 使用
proxy
代理方式來替換掉defineproperty
- 全域性和內部
API
已經被重構為支援tree-shake
option api VS composition api
composition api
是vue3
的一大特色,vue3
對外暴露了大量函式供以我們按需引用,隨意組合
在option api
中,我們通過例項化Vue
並將行為物件作為引數傳入
new Vue({
data(){
return {}
},
methods:{},
computed:{},
created(){},
...
})
在Composition api
我們可以通過setup
作為入口函式,並返回一個物件資料暴露給模板使用
<template>
<div @click="hi">{{ msg }}</div>
</template>
<script>
export default {
setup() {
const msg = ref('Hello!')
function hi() {
console.log(msg)
}
// 暴露給模板
return {
msg,
hi
}
}
}
</script>
setup
還支援另一種寫法,更加簡化了程式碼
<template>
<div @click="hi">{{ msg }}</div>
</template>
<script setup>
const msg = ref('Hello!')
function hi() {
console.log(msg)
}
</script>
當使用 <script setup>
的時候,任何在 <script setup>
宣告的頂層的繫結 (包括變數,函式宣告,以及 import
引入的內容) 都能在模板中直接使用
響應資料的定義
vue2
中把響應資料定義到data
函式的return
中
data(){
return {
...
}
}
vue3
中我們可以通過ref
和reactive
來定義響應資料
const num = ref(0)
const obj = reactive({name:'molly'})
ref:
- 在模板中可以直接使用
ref
定義的資料,在js
中則需要使用.value
來取值或賦值 - 使用
geter
,seter
方式實現響應式 - 建議使用
ref
來定義基礎資料
reactive:
- 可使用
toRefs
將其解包,在模板中直接使用對應的attribute
- 使用
proxy
代理方式實現響應式 - 建議使用
reactive
來定義複雜資料
生命週期鉤子
vue2
中提供了11個生命週期鉤子,我們可在選項物件上直接定義使用
vue3
中把生命週期鉤子單獨抽離成了一個個對應的hooks
函式,以onXXX
的形式呼叫。值得注意的是生命週期鉤子註冊函式只能在setup
期間同步使用,這就意味著beforeCreate
和created
等同於setup
執行階段。其他的週期用法基本一致,例如:mounted
對應onMounted
import { onMounted } from 'vue'
setup() {
onMounted(() => {
console.log('mounted!')
})
}
computed
兩個版本的computed
基本保持一致,不同的是vue3
將computed
抽離成一個hooks
函式使用
// vue2
computed:{
num:()=>this.num *2
}
// vue3
const numVal = computed(() => num.value *2)
watch
vue2:
watch
監聽一個特定資料來源的結果變化,回撥函式得到的引數為新值和舊值。除了監聽data
中的資料
還可以監聽props
、$route
、$emit
、computed
。可通過選項引數deep
來深度監聽,指定 immediate: true
將立即觸發回撥
watch:{
obj:(val,old)=>{
deep: true,
immediate: true
}
}
vue3
vue3
的計算屬性得到了很大的加強,支援監聽多個資料來源和執行副作用
// 監聽單個資料來源
const count = ref(0)
watch(count, (val, old) => {
/* ... */
})
// 假設count是一個物件,也支援監聽整個物件,而無需指定deep屬性
// 監聽多個資料來源
const count = ref(0)
const obj = reactive({name:'molly'})
watch([() => obj.name, count], ([newName, newCount], [oldName, oldCount]) => {
/* ... */
})
// 監聽多個資料來源時,watch函式的第一個引數可傳入資料來源陣列,第二個回撥函式的引數也是一個陣列
vue3
還新增了watchEffect
,表示立即執行傳入的一個函式,同時響應式追蹤其依賴,並在其依賴變更時重新執行該函式。
與watch
相比,watchEffect
不需要傳入指定監聽的資料來源,它會自動收集依賴。也沒有新值舊值的概念,只要依賴發生變化,就會重新執行函式
const count = ref(0)
watchEffect(() => console.log(count.value))
setTimeout(() => {
count.value++
}, 100)
watchEffect
會返回一個用於停止這個監聽的函式
當watchEffect
在元件的setup
函式或生命週期鉤子被呼叫時,偵聽器會被連結到該元件的生命週期,並在元件解除安裝時自動停止。
const stop = watchEffect(() => {
/* ... */
})
stop()
filters
vue2
中我們可以很方便的使用filters過濾器來處理一些文字格式轉換,對資料進行加工。
filters: {
sum(num1,num2) {
return num1+num2
}
}
請注意: 在vue3
中,將移除過濾器,且不再支援。官方更推薦我們使用方法呼叫或計算屬性來替換filters
至於為啥要移除這個api
官方的解釋是:雖然這看起來很方便,但它需要一個自定義語法,打破了大括號內的表示式“只是 JavaScript
”的假設,這不僅有學習成本,而且有實現成本。
我的理解是:api
功能設計重複,filters
能幹的事兒,計算屬性和函式呼叫也能幹,且乾的更好。所以尤大大含淚移除filters
,可憐的filters
只能被拋棄
人生亦是如此,職場中優勝劣汰,希望我們永遠都不會是那個被優化掉的filters
???
components
vue2
中我們需要通過選項components
進行元件註冊,而在vue3
中,我們可以直接使用元件
//vue2
import A from './a.vue'
components:{
A
}
//vue3
<template>
<A/>
</template>
<script setup>
import A from './a.vue'
</script>
指令:vue2 VS vue3
兩個版本的指令用法基本相同,這裡我只列出vue3
差異部分
v-model
vue2
中我們實現一個自定義v-model
可以這樣寫
// vue2
props:{
title:{
type:String,
default: 'molly'
}
},
model: {
prop: 'title',
event: 'change',
},
methods:{
change(val){
this.$emit('input',val)
}
},
vue3
中可以定義v-model
引數名,同時還支援設定多個v-model
,簡直美滋滋
// vue3
props:{
title:{
type:String
},
num:{
type:Number
},
},
setup(props,{emit}){
function change1(val){
emit('update:title',val)
}
function change2(val){
emit('update:num',val)
}
return {
change1,
change2
}
}
// 在父元件中使用
<Son v-model:title="molly" v-model:num="18" />
新增指令
v-memo
,該指令記住一個模板的子樹。元素和元件上都可以使用。該指令接收一個固定長度的陣列作為依賴值進行記憶比對。如果陣列中的每個值都和上次渲染的時候相同,則整個該子樹的更新會被跳過。相當於記憶體換時間,相同渲染內容則從記憶體讀取。這對於長列表場景很有用
其他變更
- 對於
v-if
/v-else
/v-else-if
的各分支項key
將不再是必須的,因為現在vue3
會自動生成唯一的key
<template v-for>
的key
應該設定在<template>
標籤上 (而不是設定在它的子節點上)。- 作用於同一個元素上時,
v-if
會擁有比v-for
更高的優先順序。 v-on
的.native
修飾符已被移除。v-for
中的ref
不再註冊ref
陣列在使用
v-bind="object"
與元件獨立屬性重名時,那麼繫結的宣告順序將決定它們如何被合併。以後者為標準<!-- 模板 --> <div id="red" v-bind="{ id: 'blue' }"></div> <!-- 結果 --> <div id="blue"></div> <!-- 模板 --> <div v-bind="{ id: 'blue' }" id="red"></div> <!-- 結果 --> <div id="red"></div>
元件通訊:vue2 VS vue3
在vue2
中提供了多種api
供以我們進行元件通訊:
props
/$emit
/$on
$attrs
/$listeners
provide
/inject
$parent
/$children
/ref
在vue3
中依然支援大部分api
,並做了適當調整
- 移除了
$on
、$off
、$once
例項方法 - 移除了
$children
例項property
$attrs
現在包含了所有傳遞給元件的attribute
,包括class
和style
- 在
<script setup>
中必須使用defineProps
和defineEmits
API 來宣告props
和emits
,它們具備完整的型別推斷並且在<script setup>
中是直接可用的
插槽 vue2 VS vue3
在插槽這一塊兩個版本基本保持一致,沒有太多的改動,還是保留原來的使用方式。vue3
做了一點點小更新
this.$slots
現在將插槽作為函式公開- 移除
this.$scopedSlots
程式碼複用 vue2 VS vue3
vue2
中程式碼複用的手段很多,主要有以下幾種方式
- 元件抽離
- 自定義指令
- 例項全域性掛載
- 外掛封裝
- mixin
- extend
vue3
中同樣涵蓋以上手段,並做了相應優化與更新
- 例項全域性掛載這一手段在
vue2
中,我們一般是簡單粗暴的通過prototype
將行為物件掛載到vue
原型上,這種方式雖然簡單明瞭,但是也存在全域性汙染的問題。
Vue.prototype.$xxx = {name:'molly'}
對應到vue3
中,則不允許我們這樣子幹,取而代之的是 app.config.globalProperties
mixin
也是程式碼複用的一大利器,不過相應的也暴露出一些問題。當mixin
被濫用或大量使用時,會導致依賴關係不明確,不易維護。在vue3
中更推薦我們使用自定義hooks
的方式來複用程式碼。
實現一個自定義hooks
我們可以把一個功能相關的資料和邏輯都抽離出來放到一起維護,例如實現一個累加器
import {ref, onUnmounted} from 'vue'
export function useAccumulator(){
const count = ref(0)
let timer = null
timer = setInterval(()=>{
count.value ++
},1000)
onUnmounted(()=>{
clearInterval(timer)
})
return {count}
}
我們定義了一個累加器的hooks
函式,在元件入口就可以像普通函式一樣使用useAccumulator()
import {useAccumulator} from '../utils'
let {count} = useAccumulator()
script setup 補充
貼上官網的描述
<script setup>
是在單檔案元件 (SFC) 中使用組合式 API的編譯時語法糖。相比於普通的 <script>
語法,它具有更多優勢:
- 更少的樣板內容,更簡潔的程式碼。
- 能夠使用純
Typescript
宣告props
和丟擲事件。 - 更好的執行時效能 (其模板會被編譯成與其同一作用域的渲染函式,沒有任何的中間代理)。
- 更好的 IDE 型別推斷效能 (減少語言伺服器從程式碼中抽離型別的工作)。
<script setup>
為我們帶來了極大的便利,優勢如下:
- 所有頂層繫結變數內容都能在模板中直接使用
<script setup>
範圍裡的值也能被直接作為自定義元件的標籤名使用,相當於我們可以不用通過components
註冊元件- 支援使用
:is
來繫結動態元件 - 支援頂層
await
,先比JavaScript
一步實現這個特性,就是爽
強大的style特性
強大的style
特性是vue3
的又一個神兵利器
深度選擇器
為了使元件之間樣式互不影響,我們可以這樣寫<style scoped>
,當處於scoped
下想做深度選擇時,可以使用:deep()
偽類
<style scoped>
.a :deep(.b) {
...
}
</style>
插槽選擇器
預設情況下,作用域樣式不會影響到<slot/>
內容,可以使用:slotted
偽類來選擇到插槽
:slotted(div) {
color: red;
}
全域性選擇器
可通過:global
偽類來實現全域性樣式
:global(.red) {
color: red;
}
css module的支援
可通過<style module>
方式將css類作為$style
物件暴露出來給元件使用,同時也支援自定義名稱:<style module=“molly” >
<template>
<p :class="molly.red">red</p>
</template>
<style module="molly">
.red {
color: red;
}
</style>
狀態驅動的動態css
這是我認為最方便的一個特性,可以讓我們少寫很多程式碼,非常爽
在style
中允許我們使用v-bind
來將css值動態關聯到元件上
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red'
}
}
}
</script>
<style>
// v-bind可以直接引入script中的響應資料作為值
.text {
color: v-bind(color);
}
</style>
好啦,總結至此應該可以輕鬆上手vue3專案了,喜歡的小夥伴歡迎點贊留言討論。點贊超過30我將持續更新差異化對比vue-router4.x 和vuex 4.x 系列
感謝
撒花✿✿ヽ(°▽°)ノ✿
撒花✿✿ヽ(°▽°)ノ✿