本文目錄:
- 1.特性函數(shù)setup
- 2.Ref 語(yǔ)法
- 3.Reactive 函數(shù)
- 4.Vue3 生命周期
- 5.偵測(cè)變化 - watch
- 6.Vue3的模塊化開(kāi)發(fā)
- 7.彈窗類組件優(yōu)化:Teleport
- 8.異步組件優(yōu)化:Suspense
- 9.全局API優(yōu)化
1.特性函數(shù)setup
1、setup函數(shù)是處于 生命周期函數(shù) beforeCreate 和 Created 兩個(gè)鉤子函數(shù)之間的函數(shù) 也就說(shuō)在 setup函數(shù)中是無(wú)法 使用 data 和 methods 中的數(shù)據(jù)和方法的
2、setup函數(shù)是 Composition API(組合API)的入口
3、在setup函數(shù)中定義的變量和方法最后都是需要 return 出去的 不然無(wú)法再模板中使用
setup第一個(gè)參數(shù)props會(huì)自動(dòng)推論成props里面定義的類型
第二個(gè)參數(shù)context,在setup中無(wú)法直接訪問(wèn)vue2最常用的this對(duì)象,context提供了vue2中最常用的三個(gè)屬性
context.attrs
context.slots
context.emit
2.Ref 語(yǔ)法
ref 是一個(gè)函數(shù),它接受一個(gè)參數(shù),返回的就是一個(gè)神奇的 響應(yīng)式對(duì)象 。我們初始化的這個(gè) 0 作為參數(shù)包裹到這個(gè)對(duì)象中去,在未來(lái)可以檢測(cè)到改變并作出對(duì)應(yīng)的相應(yīng)。
<template>
<h1>{{count}}</h1>
<h1>{{double}}</h1>
<button @click="increase">+1</button>
</template>
import { ref } from "vue"
setup() {
const count = ref(0)
const double = computed(() => {
return count.value * 2
})
const increase = () => {
count.value++
}
return {
count,
increase,
double
}
}
3.Reactive 函數(shù)
reactive的用法與ref的用法相似,也是將數(shù)據(jù)變成響應(yīng)式數(shù)據(jù),當(dāng)數(shù)據(jù)發(fā)生變化時(shí)UI也會(huì)自動(dòng)更新。不同的是ref用于基本數(shù)據(jù)類型,而reactive是用于復(fù)雜數(shù)據(jù)類型,比如對(duì)象和數(shù)組
import { ref, computed, reactive, toRefs } from 'vue'
interface DataProps {
count: number;
double: number;
increase: () => void;
}
setup() {
const data: DataProps = reactive({
count: 0,
increase: () => { data.count++},
double: computed(() => data.count * 2)
})
const refData = toRefs(data)
return {
...refData
}
}
使用 ref 還是 reactive 可以選擇這樣的準(zhǔn)則
第一,就像剛才的原生 javascript 的代碼一樣,像你平常寫普通的 js 代碼選擇原始類型和對(duì)象類型一樣來(lái)選擇是使用 ref 還是 reactive。
第二,所有場(chǎng)景都使用 reactive,但是要記得使用 toRefs 保證 reactive 對(duì)象屬性保持響應(yīng)性。
4.Vue3 生命周期
在vue3中,生命周期在hook中的變化
- beforeCreate -> 不需要
- created -> 不需要
- beforeMount -> onBeforeMount
- mounted -> onMounted
- beforeUpdate -> onBeforeUpdate
- updated -> onUpdated
- beforeUnmount -> onBeforeUnmount
- unmounted -> onUnmounted
- errorCaptured -> onErrorCaptured
- renderTracked -> onRenderTracked
- renderTriggered -> onRenderTriggered
為了更好的Tree-shaking,Vue3的生命周期函數(shù)都是從 vue 中導(dǎo)入,再進(jìn)行直接調(diào)用
從 vue 中引入 多個(gè)生命周期函數(shù)
import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted} from 'vue'
export default {
name: 'App',
setup() {
onBeforeMount(() => {
// 在掛載前執(zhí)行
})
onMounted(() => {
// 在掛載后執(zhí)行
})
onBeforeUpdate(() => {
// 在更新前前執(zhí)行
})
onUpdated(() => {
// 在更新后執(zhí)行
})
onBeforeUnmount(() => {
// 在組件銷毀前執(zhí)行
})
unMounted(() => {
// 在組件銷毀后執(zhí)行
})
return {}
}
}
onErrorCaptured可以捕捉錯(cuò)誤,同時(shí)捕捉錯(cuò)誤需要返回一個(gè)布爾值,表示錯(cuò)誤是否向上傳播
import { onErrorCaptured } from 'vue'
setup(){
const error = ref(null)
onErrorCaptured ((e:any)=>{
error.value = e
return true
})
}
5.偵測(cè)變化 - watch
watch的第一個(gè)參數(shù)是一個(gè)響應(yīng)式的對(duì)象,如果要監(jiān)聽(tīng)多個(gè)對(duì)象,則第一個(gè)參數(shù)也可以是一個(gè)數(shù)組
watch 多個(gè)值,返回的也是多個(gè)值的數(shù)組
watch([greetings, data], (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated' + greetings.value + data.count
})
如果是打算監(jiān)聽(tīng)data中的一個(gè)屬性,比如data.count,則不能直接去watch([greetings, data.count]這樣寫,而是要使用 getter 的寫法 watch reactive 對(duì)象中的一項(xiàng)
watch([greetings, () => data.count], (newValue, oldValue) => {
console.log('old', oldValue)
console.log('new', newValue)
document.title = 'updated' + greetings.value + data.count
})
6.Vue3的模塊化開(kāi)發(fā)
使用composition API 的好處
- 將相關(guān)的邏輯代碼組合在一起,不要分散在代碼的各個(gè)地方
- 可以非常容易的重用代碼,比mixin有更高的靈活度
實(shí)踐:記錄鼠標(biāo)的位置
利用vue3進(jìn)行初步的實(shí)現(xiàn),將所有的代碼都寫在setup中
const x = ref(0)
const y = ref(0)
const updateMouse = (e: MouseEvent) => {
x.value = e.pageX
y.value = e.pageY
}
onMounted(() => {
document.addEventListener('click', updateMouse)
})
onUnmounted(() => {
document.removeEventListener('click', updateMouse)
})
但是上方的代碼還不能做到很方便的重用,所以接下來(lái)需要對(duì)代碼進(jìn)行改造,將組件內(nèi)邏輯抽象成可復(fù)用的函數(shù)
新建一個(gè)文件hooks文件夾,里面就存放我們抽離出來(lái)的所有的邏輯模塊,新建一個(gè)useMousePosition.ts
import { ref, onMounted, onUnmounted } from 'vue'
function useMouseTracker() {
// const positions = reactive<MousePostion>({
// x: 0,
// y: 0
// })
const x = ref(0)
const y = ref(0)
const updatePosition = (event: MouseEvent) => {
x.value = event.clientX
y.value = event.clientY
}
onMounted(() => {
document.addEventListener('click', updatePosition)
})
onUnmounted(() => {
document.removeEventListener('click', updatePosition)
})
return { x, y }
}
export default useMouseTracker
在需要使用這個(gè)邏輯的頁(yè)面進(jìn)行引用
import useMousePosition from './hooks/useMousePosition'
這里引用到的就是一段函數(shù)代碼,然后調(diào)用函數(shù),取其返回值就可以了
const { x, y } = useMousePosition()
vue3 這種實(shí)現(xiàn)方式的優(yōu)點(diǎn)
- 第一:它可以清楚的知道 xy 這兩個(gè)值的來(lái)源,這兩個(gè)參數(shù)是干什么的,他們來(lái)自 useMouseTracker 的返回,那么它們就是用來(lái)追蹤鼠標(biāo)位置的值。
- 第二:我們可以xy 可以設(shè)置任何別名,這樣就避免了命名沖突的風(fēng)險(xiǎn)。
- 第三:這段邏輯可以脫離組件存在,因?yàn)樗緛?lái)就和組件的實(shí)現(xiàn)沒(méi)有任何關(guān)系,我們不需要添加任何組件實(shí)現(xiàn)相應(yīng)的功能。只有邏輯代碼在里面,不需要模版。
將請(qǐng)求接口的邏輯抽離出去:
import { ref } from 'vue'
import axios from 'axios'
// 添加一個(gè)參數(shù)作為要使用的 地址
const useURLLoader = (url: string) => {
// 聲明幾個(gè)ref,代表不同的狀態(tài)和結(jié)果
const result = ref(null)
const loading = ref(true)
const loaded = ref(false)
const error = ref(null)
// 發(fā)送異步請(qǐng)求,獲得data
// 由于 axios 都有定義,所以rawData 可以輕松知道其類型
axios.get(url).then((rawData) => {
loading.value = false
loaded.value = true
result.value = rawData.data
}).catch((e) => {
error.value = e
})
// 將這些ref 一一返回
return {
result,
loading,
error,
loaded
}
}
export default useURLLoader
在要使用接口的頁(yè)面進(jìn)行引用:
const { result, loading, loaded } = useURLLoader('https://dog.ceo/api/breeds/image/random')
...
<h1 v-if="loading">Loading!...</h1>
<img v-if="loaded" :src="result.message" >
模塊化結(jié)合typescript - 泛型改造:
function useURLLoader<T>(url: string) {
const result = ref<T | null>(null)
在應(yīng)用中的使用,可以定義不同的數(shù)據(jù)類型
// 在應(yīng)用中的使用,可以定義不同的數(shù)據(jù)類型
interface DogResult {
message: string;
status: string;
}
interface CatResult {
id: string;
url: string;
width: number;
height: number;
}
const { result, loading, loaded } = useURLLoader<CatResult[]>('https://api.thecatapi.com/v1/images/search?limit=1')
組件的定義和導(dǎo)出的都是一個(gè)普通的object
const component = {
name: 'HelloWorld',
props: {
...
}
}
export default component;
而普通的對(duì)象在書寫代碼的時(shí)候自然不會(huì)獲得ts的提示支持,vue3提供了defineComponent來(lái)讓普通的object變?yōu)橹С謙s的對(duì)象
const component =
defineComponent({
name: 'HelloWorld',
props: {
...
}
})
export default component;
7.彈窗類組件優(yōu)化:Teleport
Vue2在彈窗組件開(kāi)發(fā)中,Dialog被包裹在其他組件之中,容易被干擾,樣式也在其他組件中,容易變得非常混亂。
vue3 新添加了一個(gè)默認(rèn)的組件就叫 Teleport,我們可以拿過(guò)來(lái)直接使用,它上面有一個(gè) to 的屬性,它接受一個(gè)css query selector 作為參數(shù),這就是代表要把這個(gè)組件渲染到哪個(gè) dom 元素中
<template>
<teleport to="#modal">
<div id="center">
<h1>this is a modal</h1>
</div>
</teleport>
</template>
特別適合彈窗類的元素渲染,這樣頁(yè)面的所有元素就不用所有的元素都擠在一個(gè)div中了
在index.html添加一個(gè)modal元素
<div id-"app"></div>
<div id-"modal"></div>
實(shí)際應(yīng)用中,在使用彈窗類元素的組件中需要手動(dòng)控制該元素的渲染與否。
定義一個(gè)isOpen來(lái)控制,
子組件內(nèi)部通過(guò)context.emit('close-modal')去觸發(fā)根組件中定義的事件,注意在vue3中子組件需要通過(guò)emit引入該事件。
<template>
<teleport to="#modal">
<div id="center" v-if="isOpen">
<h2><slot>this is a modal</slot></h2>
<button @click="buttonClick">Close</button>
</div>
</teleport>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
props: {
isOpen: Boolean,
},
emits: {
'close-modal': null
},
setup(props, context) {
const buttonClick = () => {
context.emit('close-modal')
}
return {
buttonClick
}
}
})
在 App.vue 組件中使用
const modalIsOpen = ref(false)
const openModal = () => {
modalIsOpen.value = true
}
const onModalClose = () => {
modalIsOpen.value = false
}
<button @click="openModal">Open Modal</button><br/>
<modal :isOpen="modalIsOpen" @close-modal="onModalClose"> My Modal !!!!</modal>
8.異步組件優(yōu)化:Suspense
Suspense是Vue3推出的一個(gè)內(nèi)置的特殊組件
Suspense內(nèi)部默認(rèn)有兩個(gè)自定義的slot,剛開(kāi)始的會(huì)渲染#default部分,達(dá)到某個(gè)條件之后才會(huì)去渲染#fallback的內(nèi)容。
如果要使用Suspense,組件中要返回一個(gè)Promise
可以非常方便為異步請(qǐng)求的等待界面進(jìn)行個(gè)性化的定制。
定義一個(gè)異步組件,在 setup 返回一個(gè) Promise,AsyncShow.vue
<template>
<h1>{{result}}</h1>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
export default defineComponent({
setup() {
return new Promise((resolve) => {
setTimeout(() => {
return resolve({
result: 42
})
}, 3000)
})
}
})
</script>
在 App.vue 中使用
<Suspense>
<template #default>
<async-show />
</template>
<template #fallback>
<h1>Loading !...</h1>
</template>
</Suspense>
default可以存放多個(gè)異步組件,這樣會(huì)等到所有的異步組件都拿到結(jié)果之后才去渲染里面的內(nèi)容。
<template>
<img :src="result && result.message">
</template>
<script lang="ts">
import axios from 'axios'
import { defineComponent } from 'vue'
export default defineComponent({
async setup() {
const rawData = await axios.get('https://dog.ceo/api/breeds/image')
return {
result: rawData.data
}
}
})
</script>
Suspense 中可以添加多個(gè)異步組件
<Suspense>
<template #default>
<async-show />
<dog-show />
</template>
<template #fallback>
<h1>Loading !...</h1>
</template>
</Suspense>
9.全局API優(yōu)化
vue2全局API的寫法
1.從vue中導(dǎo)出一個(gè)全局對(duì)象Vue
import Vue from 'vue'
import App from './App.vue'
2.然后在Vue中進(jìn)行一系列的配置
Vue.config.ignoredElements = [/^app-/]
Vue.use(/* ... */)
Vue.mixin(/* ... */)
Vue.component(/* ... */)
Vue.directive(/* ... */)
3.新建vue實(shí)例,然后調(diào)用$mount方法將其掛在到app節(jié)點(diǎn)上
new Vue({
render: h => h(App)
}).$mount('#app')
Vue2 這樣寫在一定程度上修改了 Vue 對(duì)象的全局狀態(tài)。
第一,在單元測(cè)試中,全局配置非常容易污染全局環(huán)境,用戶需要在每次 case 之間,保存和恢復(fù)配置。有一些 api (vue use vue mixin)甚至沒(méi)有方法恢復(fù)配置,這就讓一些插件的測(cè)試非常的困難。
第二,在不同的 APP 中,如果想共享一份有不同配置的 vue 對(duì)象,也變得非常困難。
vue3新增加了createApp,先將createApp引入,然后利用createApp生成一個(gè)app實(shí)例,
const app = createApp(App)
接下來(lái)增加配置就不需要直接給Vue添加了,而是給app添加就行了
app.config.isCustomElement = tag => tag.startsWith('app-')
app.use(/* ... */)
app.mixin(/* ... */)
app.component(/* ... */)
app.directive(/* ... */)
現(xiàn)在再設(shè)置任何的配置都會(huì)是在不同的app實(shí)例上,不同的設(shè)置就不會(huì)發(fā)生沖突。
當(dāng)配置結(jié)束以后,我們?cè)侔?App 使用 mount 方法掛載到固定的 DOM 的節(jié)點(diǎn)上。
app.mount(App, '#app')