vue3.0正式版本已經發布超過一年,社區生態已經完善,所以,是時候開始使用vue3.0了
目前已支持 vue3 的UI組件庫
- ant-design-vue
https://antdv.com/docs/vue/introduce-cn/
ant-design-vue 是 Ant Design 的 Vue 實現,組件的風格與 Ant Design 保持同步
目前支持 Vue 3.0 的 2.0.0 測試版 已發布 - element-plus
https://element-plus.gitee.io/#/zh-CN
Element Plus,一套為開發者、設計師和產品經理准備的基於 Vue 3.0 的桌面端組件庫 - vant
https://vant-contrib.gitee.io/vant/v3/#/zh-CN
輕量、可靠的移動端 Vue 組件庫
Vant 是有贊前端團隊開源的移動端組件庫,於 2016 年開源,已持續維護 4 年時間
目前 Vant 已完成了對 Vue 3.0 的適配工作,並發布了 Vant 3.0 版本
Vue3帶來的新變化
- 性能提升
- 首次渲染更快
- diff算法更快
- 內存占用更少
- 打包體積變小
- 更好的Typescript支持
- Composition API (重點)
在使用vue2.x版本開發較復雜的組件時,邏輯難以復用,組合式api的出現可以解決此類問題
相關閱讀:
- Vue3 中文文檔 https://vue3js.cn/docs/zh/
- Vue3 設計理念 https://vue3js.cn/vue-composition/ 破壞性語法更新
vue3.0對於2.0版本的大部分語法都是可以兼容的,但是也有一些破壞性的語法更新,需要格外注意
- 實例方法$on移除 (eventBus現有實現模式不再支持 可以使用三方插件替代)
- 過濾器filter移除 (插值表達式里不能再使用過濾器 可以使用methods替代)
- .sync語法移除 (和v-model語法合並)
Vue3開發環境搭建
項目搭建成功后,可以看一下package.json文件,在dependencies配置項中顯示,當前使用的版本為3.0.0
"dependencies": {
"core-js": "^3.6.5",
"vue": "^3.0.0"
}
項目環境可以查看我寫的另一篇文章:https://www.cnblogs.com/panwudi/p/16086040.html
然后打開main.js 入口文件,發現Vue的實例化發生了一些變化,由先前的new關鍵詞實例化,轉變為createApp方法的調用形式
// vue2.x
new Vue({
el: '#app',
render: h => h(App)
})
// vue3.x
import { createApp } from 'vue'
import App from './App.vue'
createApp(App).mount('#app')
打開一個單文件組件發現,vue3.0的單文件組件中不再強制要求必須有唯一根元素
<template>
<img alt="Vue logo" src="./assets/logo.png">
<HelloWorld msg="Welcome to Your Vue.js App"/>
</template>
組合式API
組合式api(Composition API)算是vue3對開發者來說非常有價值的一個api更新,先不關注具體語法,先對它有一個大的感知
composition vs options
options API開發出來的vue應用如左圖所示,它的特點是理解容易,因為各個選項都有固定的書寫位置,比如響應式數據就寫到data選擇中,操作方法就寫到methods配置項中等,應用大了之后,相信大家都遇到過來回上下找代碼的困境
composition API開發的vue應用如右圖所示,它的特點是特定功能相關的所有東西都放到一起維護,比如功能A相關的響應式數據,操作數據的方法等放到一起,這樣不管應用多大,都可以快讀定位到某個功能的所有相關代碼,維護方便,設置如果功能復雜,代碼量大,還可以進行邏輯拆分處理
特別注意:
- 選項式api和組合式api倆種風格是並存的關系 並不是非此即彼
- 需要大量的邏輯組合的場景,可以使用compition API進行增強
案例對比
1.案例1
倆個獨立的功能:
- 通過點擊按鈕來控制div的顯示和隱藏
- 通過點擊按鈕控制div內字體顏色的變化
vue2.x option Api版本
<template>
<div>
<!-- 功能一模板 -->
<button @click="show">顯示</button>
<button @click="hide">隱藏</button>
<div v-if="showDiv">一個被控制顯隱的div</div>
</div>
<div>
<!-- 功能二模板 -->
<button @click="changeRed">紅色</button>
<button @click="changeYellow">藍色</button>
<div :style="`color:${fontColor}`">一個被控制字體顏色的的div</div>
</div>
</template>
<script>
export default {
name: 'App',
data() {
return {
showDiv: true, // 功能一數據
fontColor: '' // 功能二數據
}
},
methods: {
// 功能一方法
show() {
this.showDiv = true
},
hide() {
this.showDiv = false
},
// 功能二方法
changeRed() {
this.fontColor = 'red'
},
changeYellow() {
this.fontColor = 'blue'
}
}
}
</script>
vue3.0 composition api版本
<template>
<div>
<!-- 功能一模板 -->
<button @click="show">顯示</button>
<button @click="hide">隱藏</button>
<div v-if="showDivFlag">一個被控制顯隱的div</div>
</div>
<div>
<!-- 功能二模板 -->
<button @click="changeRed">紅色</button>
<button @click="changeBlue">藍色</button>
<div :style="`color:${fontColor}`">一個被控制字體顏色的的div</div>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
name: 'App',
setup() {
// 功能一
const showDivFlag = ref(true)
function show() {
showDivFlag.value = true
}
function hide() {
showDivFlag.value = false
}
// 功能二
const fontColor = ref('')
function changeRed() {
fontColor.value = 'red'
}
function changeBlue() {
fontColor.value = 'blue'
}
return { showDivFlag, show, hide, fontColor, changeRed, changeBlue }
}
}
</script>
composition api版本優化
<script>
import { ref } from 'vue'
function useShow() {
const showDivFlag = ref(true)
function show() {
showDivFlag.value = true
}
function hide() {
showDivFlag.value = false
}
return { showDivFlag, show, hide }
}
function useColor() {
const fontColor = ref('')
function changeRed() {
fontColor.value = 'red'
}
function changeBlue() {
fontColor.value = 'blue'
}
return { fontColor, changeRed, changeBlue }
}
export default {
name: 'App',
setup() {
// 功能一
const { showDivFlag, show, hide } = useShow()
// 功能二
const { fontColor, changeRed, changeBlue } = useColor()
return { showDivFlag, show, hide, fontColor, changeRed, changeBlue }
}
}
</script>
通過定義功能函數,把倆個功能相關的代碼各自抽離到一個獨立的小函數中,然后通過在setUp函數中再把倆個小功能函數組合起來,這樣一來,既可以把setup函數變得清爽,又可以方便維護快速定位功能位置
setup入口函數
- setup 函數是一個新的組件選項,作為組件中組合式API 的起點(入口)
- setup 中不能使用 this, this 指向 undefined
- setup函數只會在組件初始化的時候執行一次
- setup函數在beforeCreate生命周期鈎子執行之前執行
export default {
setup () {
console.log('setup執行了')
console.log(this)
},
beforeCreate() {
console.log('beforeCreate執行了')
console.log(this)
}
}
響應式系統API
reactive 函數
作用:reactive是一個函數,接收一個普通的對象傳入,把對象數據轉化為響應式對象並返回
使用步驟
- 從vue框架中導入
reactive
函數 - 在setup函數中調用reactive函數並將對象數據傳入
- 在setup函數中把reactive函數調用完畢之后的返回值以對象的形式返回出去
<template>
<div>{{ state.name }}</div>
<div>{{ state.age }}</div>
<button @click="state.name = 'yxx'">改值</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup () {
const state = reactive({
name: 'yx',
age: 18
})
return {
state
}
}
}
</script>
ref 函數
作用:ref是一個函數,接受一個簡單類型或者復雜類型的傳入並返回一個響應式且可變的 ref 對象
使用步驟
- 從vue框架中導出
ref
函數 - 在setup函數中調用
ref
函數並傳入數據(簡單類型或者復雜類型) - 在setup函數中把ref函數調用完畢的返回值以對象的形式返回出去
- 注意:在setup函數中使用ref結果,需要通過
.value
訪問,模板中使用不需要加.value
<template>
<div>{{ money }}</div>
<button @click="changeMondy">改值</button>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
let money = ref(18)
console.log(money.value)
return {
money
}
}
}
</script>
總結說明:
- ref 函數可以接收一個簡單類型的值,返回一個可改變的 ref 響應式對象,從而彌補reactive函數不支持簡單類型的問題
- reactive和ref函數都可以提供響應式數據的轉換,具體什么時候需要使用哪個API社區還沒有最佳實踐,大家暫時可以使用自己熟練的API進行轉換
- 推薦一種寫法:只有明確知道要轉換的對象內部的字段名稱我們才使用reactive,否則就一律使用ref,從而降低在語法選擇上的心智負擔
toRefs 函數
注意:經過reactive函數處理之后返回的對象,如果給這個對象解構或者展開,會讓數據丟失響應式的能力,為了解決這個問題需要引入toRefs函數,使用 toRefs函數 可以保證該對象展開的每個屬性都是響應式的
使用之前reactive函數的例子,如果想在模板中省略到state,直接書寫name和age,你可能會想到,那我在return出去的時候把state中的屬性解構.
<template>
<div>{{ name }}</div>
<div>{{ age }}</div>
<button @click="name = 'yxx'">改值</button>
</template>
<script>
import { reactive } from 'vue'
export default {
setup() {
const state = reactive({
name: 'yx',
age: 18
})
return {
...state
}
}
}
</script>
點擊改值按鈕,發現視圖已經不發生變化了,如果解構reactive的返回值,將破壞調用響應式特性,這時就需要使用toRefs方法進行處理了
經過toRefs包裹處理會發現,數據恢復了響應式特性
<template>
<div>{{ name }}</div>
<div>{{ age }}</div>
<button @click="name = 'yxx'">改值</button>
</template>
<script>
import { reactive,toRefs } from 'vue'
export default {
setup() {
const state = reactive({
name: 'yx',
age: 18
})
return {
...toRefs(state)
}
}
}
</script>
computed計算屬性
作用:根據現有響應式數據經過一定的計算得到全新的數據
下面講介紹如何在vue3中使用計算屬性
使用步驟
- 從vue框架中導入
computed
函數 - 在setup函數中執行computed函數,並傳入一個函數,在函數中定義計算公式
- 把computed函數調用完的執行結果放到setup的return值對象中
<template>
{{ list }}
{{ filterList }}
<button @click="changeList">change list</button>
</template>
<script>
import { computed, ref } from 'vue'
export default {
setup() {
const list = ref([1, 2, 3, 4, 5])
// 輸入大於3的數字
const filterList = computed(() => {
return list.value.filter(item => item > 3)
})
// 修改list的函數
function changeList() {
list.value.push(6, 7, 8)
}
return {
list,
filterList,
changeList
}
}
}
</script>
watch 偵聽器
作用:基於響應式數據的變化執行回調邏輯,和vue2中的watch的功能完全一致
- 普通監聽
- 立即執行
- 深度監聽
下面講介紹如何在vue3中使用偵聽器
使用步驟
- 從vue框架中導入
watch
函數 - 在setup函數中執行watch函數開啟對響應式數據的監聽
- watch函數接收三個常規參數
- 第一個參數為函數,返回你要監聽變化的響應式數據
- 第二個參數為響應式數據變化之后要執行的回調函數
- 第三個參數為一個對象,在里面配置是否開啟立刻執行或者深度監聽
(1)普通監聽
<template>
{{ age }}
<button @click="age++">change age</button>
</template>
<script>
import { ref, watch } from 'vue'
export default {
setup() {
const age = ref(18)
watch(() => {
// 返回你想要監聽的響應式屬性(ref產生的對象必須加.value)
return age.value
}, () => {
// 數據變化之后的回調函數
console.log('age發生了變化')
})
return {
age
}
}
}
</script>
(2)開啟立即執行
watch的效果默認狀態下,只有監聽的數據發生變化才會執行回調,如果你需要在一上來的時候就立刻執行一次,需要配置一下immediate屬性
<template>
{{ age }}
<button @click="age++">change age</button>
</template>
<script>
import { ref, watch } from 'vue'
export default {
setup() {
const age = ref(18)
watch(() => {
// 返回你想要監聽的響應式屬性(ref產生的對象必須加.value)
return age.value
}, () => {
// 數據變化之后的回調函數
console.log('age發生了變化')
},{ immediate: true})
return {
age
}
}
}
</script>
(3)開啟深度監聽
當我們監聽的數據是一個對象的時候,默認狀態下,對象內部的屬性發生變化是不會引起回調函數執行的,如果想讓對象下面所有屬性都能得到監聽,需要開啟deep配置
<template>
{{ name }}
{{ info.age }}
<button @click="name = 'pink'">change name</button>
<button @click="info.age++">change age</button>
</template>
<script>
import { reactive, toRefs, watch } from 'vue'
export default {
setup() {
const state = reactive({
name: 'yx',
info: {
age: 18
}
})
watch(() => {
return state
}, () => {
// 數據變化之后的回調函數
console.log('age發生了變化')
}, {
deep: true
})
return {
...toRefs(state)
}
}
}
</script>
使用watch的時候,盡量詳細的表明你到底要監聽哪個屬性,開啟deep深度監聽后,會無腦監聽當前對象的全部屬性,避免使用deep引起的性能問題,比如我僅僅只是想在state對象的age屬性變化的時候執行回調
<template>
{{ name }}
{{ info.age }}
<button @click="name = 'pink'">change name</button>
<button @click="info.age++">change age</button>
</template>
<script>
import { reactive, toRefs, watch } from 'vue'
export default {
setup() {
const state = reactive({
name: 'yx',
info: {
age: 18
}
})
watch(() => {
// 詳細的告知你要監聽誰
return state.info.age
}, () => {
// 數據變化之后的回調函數
console.log('age發生了變化')
})
return {
...toRefs(state)
}
}
}
</script>
生命周期函數
使用步驟
- 先從vue中導入以
on打頭
的生命周期鈎子函數 - 在setup函數中調用生命周期函數並傳入回調函數
- 生命周期鈎子函數可以調用多次
<template>
<div>生命周期函數</div>
</template>
<script>
import { onMounted } from 'vue'
export default {
setup() {
// 時機成熟 回調函數自動執行
onMounted(() => {
console.log('mouted生命周期執行了')
})
onMounted(() => {
console.log('mouted生命周期函數又執行了')
})
}
}
</script>
選項式API |
組合式API |
beforeCreate |
不需要(直接寫到setup函數中) |
created |
不需要(直接寫到setup函數中) |
beforeMount |
onBeforeMount |
mounted |
onMounted |
beforeUpdate |
onBeforeUpdate |
updated |
onUpdated |
beforeDestroyed |
onBeforeUnmount |
destroyed |
onUnmounted |
父子通信
在vue3的組合式API中,父傳子的基礎套路完全一樣,基礎思想依舊為:父傳子是通過prop進行傳入,子傳父通過調用自定義事件完成
實現步驟
- setup函數提供倆個參數,第一個參數為props,第二個參數為一個對象context
- props為一個對象,內部包含了父組件傳遞過來的所有prop數據,context對象包含了attrs,slots, emit屬性,其中的emit可以觸發自定義事件的執行從而完成子傳父
代碼落地
app.vue
<template>
<son :name="name" @get-msg="getMsg"></son>
</template>
<script>
import { ref } from 'vue'
import Son from './components/son'
export default {
components: {
Son
},
setup() {
const name = ref('cp')
function getMsg(msg) {
console.log(msg)
}
return {
name,
getMsg
}
}
}
</script>
<template>
<div>
{{name}}
<button @click="setMsgToSon">set</button>
</div>
</template>
<script>
export default {
props: {
name: {
type: String
}
},
emits: ['get-msg'], // 聲明當前組件觸發的自定義事件
setup(props,{emit}) {
console.log(props.name)
function setMsgToSon(){
emit('get-msg','這是一條來自子組件的msg信息')
}
return {
setMsgToSon
}
}
}
</script>
provide 和 inject
通常我們使用props進行父子之間的數據傳遞,但是如果組件嵌套層級較深,一層一層往下傳遞將會變的非常繁瑣,有沒有一種手段可以把這個過程簡化一下呢,有的,就是我們馬上要學習的provide 和 inject,它們配合起來可以方便的完成跨層傳遞數據
1. 基礎使用
來個需求: 爺組件中有一份數據 傳遞給孫組件直接使用
實現步驟:
- 頂層組件在setup方法中使用
provide函數
提供數據 - 任何底層組件在setup方法中使用
inject函數
獲取數據
代碼落地
爺爺組件 - app.vue
<template>
<father></father>
</template>
<script>
import Father from '@/components/Father'
import { provide } from 'vue'
export default {
components: {
Father
},
setup() {
let name = '你想吃火鍋嗎'
// 使用provide配置項注入數據 key - value
provide('name', name)
}
}
</script>
<template>
我是子組件
{{ name }}
</template>
<script>
import { inject } from 'vue'
export default {
setup() {
const name = inject('name')
return {
name
}
}
}
</script>
事實上,只要是后代組件,都可以方便的獲取頂層組件提供的數據
2. 傳遞響應式數據
provide默認情況下傳遞的數據不是響應式的,也就是如果對provide提供的數據進行修改,並不能響應式的影響到底層組件使用數據的地方,如果想要傳遞響應數據也非常簡單,只需要將傳遞的數據使用ref或者reactive生成即可
掌握如何通過provide/inject傳遞響應式數據
app.vue
<template>
<father></father>
<button @click="changeName">change name</button>
</template>
<script>
import Father from '@/components/Father'
import { provide, ref } from 'vue'
export default {
components: {
Father
},
setup() {
// 使用ref轉換成響應式再傳遞
let name = ref('你想吃火鍋嗎')
function changeName(){
name.value = 'pink'
}
provide('name', name)
return {
changeName
}
}
}
</script>
模板中 ref 的使用
在模板中使用ref,我們都很清楚,它一般有三種使用場景
- ref + 普通dom標簽 獲取真實dom對象
- ref + 組件標簽 獲取組件實例對象
- ref + v-for 獲取由dom對象(實例對象)組成的數組 (不經常使用)
實現步驟
- 使用ref函數傳入null創建 ref對象 =>
const hRef = ref(null)
- 模板中通過定義ref屬性等於1中創建的ref對象名稱建立關聯 =>
<h1 ref="hRef"></h1>
- 使用 =>
hRef.value
代碼落地
components/RefComponent.vue
<template>
我是一個普通的組件
</template>
<template>
<h1 ref="h1Ref">我是普通dom標簽</h1>
<ref-comoonent ref="comRef"></ref-comoonent>
</template>
<script>
import { onMounted, ref } from 'vue'
import RefComoonent from '@/components/RefComponent'
export default {
components: {
RefComoonent
},
setup() {
const h1Ref = ref(null)
const comRef = ref(null)
onMounted(() => {
console.log(h1Ref.value)
console.log(comRef.value)
})
// 必須return
return {
h1Ref,
comRef
}
}
}
</script>