前言
Vue3已經(jīng)發(fā)布很長(zhǎng)一段時(shí)間了,雖然早就用上了框架,但是很多人依舊保持著Vue2的思維習(xí)慣,導(dǎo)致大家在實(shí)際開(kāi)發(fā)中并沒(méi)有感覺(jué)到提升,屬實(shí)是新瓶裝舊酒。我們應(yīng)該意識(shí)到這并不僅僅是一個(gè)數(shù)字大版本的迭代,而是一次全新的開(kāi)發(fā)體驗(yàn)。
讓我們一起看看在使用Vue3開(kāi)發(fā)時(shí),應(yīng)該在哪些地方做出改變?
正文
使用<script setup>
如果是從Vue2轉(zhuǎn)到Vue3,我們很熟悉的一種寫法是選項(xiàng)式API寫法。
?<template> <div :class="$style.container"> <div class="title">{{ title }}</div> <CompA /> </div></template><script>import CompA from './CompA.vue'export default { components: { CompA }, setup (props, context) { return {} }, methods: { doSomething () { // do something }, },}</script><style lang="scss" module></style>
通過(guò)export default導(dǎo)出一個(gè)對(duì)象,內(nèi)部的data,methods,watch都可以使用,this依然可以保留,并指向vue,setup中如果想用props、emit等,通過(guò)參數(shù)傳遞。在setup中可以直接使用新寫法,組件通過(guò)components進(jìn)行引入??梢詷O大的還原Vue2的用法,如果團(tuán)隊(duì)的組件庫(kù)是使用Vue2寫的,可以用很小的成本就改造完成。
為了更好的類型推導(dǎo),vue還提供了defineComponent方法。
export default defineComponent({ components: { CompA }, setup (props, context) { // do something },})
但其實(shí)官方并不推薦這種寫法,這種寫法僅僅是為了兼容舊代碼,這也是你感覺(jué)Vue3沒(méi)有提升的很大一方面因素。就像是iPhone更新,當(dāng)外觀有變化時(shí)你才會(huì)覺(jué)得是大更新,系統(tǒng)升級(jí)個(gè)IOS18,你覺(jué)得卵用沒(méi)用。所以更好的方式應(yīng)該是<script setup>標(biāo)簽對(duì)的寫法。
<script setup lang="ts">import { onMounted, ref } from 'vue'const title = ref<string>('')onMounted(() => { title.value = 'Demo'})</script>
<template> <div :class="$style.container">{{ title }}</div></template>
<style lang="scss" module></style>
你會(huì)發(fā)現(xiàn)有很多核心的變化,首先不再需要export導(dǎo)出了,標(biāo)簽對(duì)內(nèi)直接就是一個(gè)setup環(huán)境。ref可以直接寫,也沒(méi)有了methods,你寫一個(gè)就是一個(gè)方法,直接就可以綁定。為什么呢?官方不是說(shuō)所有的值都需要return出去嗎?放心,@vue/compiler-sfc幫你解決了這些煩惱。
其次這種寫法是去this化的,比如以往我們調(diào)用router都是this.$router這么使用,而現(xiàn)在你需要引入useRouter,可以更好的分辨來(lái)源。對(duì)ts也更友好。
import { useRouter } from 'vue-router'const router = useRouter()
組件使用也更方便,直接引入即可。
<script setup lang="ts">import CompA from './CompA.vue'</script><template> <div :class="$style.container"> <CompA /> </div></template>
同名簡(jiǎn)寫
以往我們綁定一個(gè)值需要這樣:
<template> <div :id="id"> <Comp :title="title" /> </div></template><script>export default { data () { return { id: 'container', title: '標(biāo)題', } },}</script>
而現(xiàn)在變得極其簡(jiǎn)單,尤其是Vue升級(jí)到v3.4.x以上之后,因?yàn)樗黾恿送?jiǎn)寫。
<script setup lang="ts">const id = ref('container')const title = ref('標(biāo)題')</script><template> <div :id> <CompA :title /> </div></template>
怎么樣,有沒(méi)有覺(jué)得非常優(yōu)雅,不過(guò)比較可惜的是這個(gè)寫法esLint目前還不支持,會(huì)報(bào)異常,需要在.eslintrc中忽略一下。
"rules": { "vue/valid-v-bind": "off"}
拒絕mixins
我們之前Vue2的模版中有很多的mixins,而且不乏有全局引入的mixins,在遷移模板時(shí),也需要一起處理,我看到官方也有案例,有mixins的,還有extends的。
const mixin = { created() { console.log(1) } }createApp({ created() { console.log(2) }, mixins: [mixin] })
但是均都對(duì)組合式API不友好,因?yàn)?code style="-webkit-tap-highlight-color: transparent; margin: 0px 2px; padding: 2px 4px; outline: 0px; max-width: 100%; box-sizing: border-box !important; overflow-wrap: break-word !important; border-radius: 4px; background-color: rgba(27, 31, 35, 0.05); font-family: "Operator Mono", Consolas, Monaco, Menlo, monospace; word-break: break-all;">mixin內(nèi)部有不少調(diào)用this內(nèi)部環(huán)境的地方,很難在<script setup>中使用,而且mixins最大的問(wèn)題就是,你無(wú)法溯源,別人在某個(gè)犄角旮旯引入一個(gè)全局mixins,你根本找不到,而且也對(duì)類型推導(dǎo)極其不友好。所以建議使用組合式函數(shù)代替。比如我們之前有一個(gè)NavBar的mixins,里面處理了很多邏輯,就可以用組合式函數(shù)進(jìn)行封裝。
import { onMounted, ref } from 'vue'
const commonProps = {}const useNavbar = () => { const navbarProps = ref<any>({}) const setNavbar = (newProps?: any) => { navbarProps.value = { ...navbarProps.value, ...newProps || {}, ...commonProps, } if (navbarProps.value.title && typeof document !== 'undefined') { document.title = navbarProps.value.title } } onMounted(() => { // init }) return { navbarProps, setNavbar, }}export default useNavbar
在使用時(shí)引用進(jìn)來(lái)即可,而且只要你調(diào)用了useNavbar,內(nèi)部的onMounted也會(huì)執(zhí)行,非常方便。
import useNavbar from './useNavbar'const { navbarProps, setNavbar} = useNavbar()
減少全局變量
之前在Vue2時(shí),我們經(jīng)常會(huì)將一些常用屬性掛載在Vue.prototype原型上,方便內(nèi)部用this.xxx使用。比如我們會(huì)把Request掛載上去,Vue.prototype.$request = Request。我們發(fā)送請(qǐng)求時(shí)直接this.$request即可,很方便。其實(shí)很多Vue2的依賴庫(kù)都是這么寫的,比如vue-router,就是在install中將$router寫為了全局變量,在我們使用Vue.use(Router)后,方便我們使用。

vue-router源碼而在Vue3中也有替代方案,app.config.globalProperties.$request = Request。但是在使用時(shí)就比較麻煩了,因?yàn)闆](méi)有this環(huán)境,需要從實(shí)例上取。
import { getCurrentInstance } from 'vue'const $this = getCurrentInstance()?.appContext.config.globalProperties$this?.$request.post('/url', {})
很深的API,既然這么深,我想我封裝一下吧。
// vueThis.tsimport { getCurrentInstance } from 'vue'export default getCurrentInstance()?.appContext.config.globalProperties
// 使用時(shí)import vueThis from './vueThis'vueThis.$request.post('/url',{})
但是你說(shuō)氣人不,getCurrentInstance還要識(shí)別調(diào)用的時(shí)機(jī),你直接賦值,相當(dāng)于引入時(shí)就運(yùn)行了,這個(gè)時(shí)候還沒(méi)實(shí)例,你還得閉包包一下,調(diào)用也不好看。
// vueThis.tsexport default () => { return getCurrentInstance()?.appContext.config.globalProperties}
// 使用時(shí)import vueThis from './vueThis'const $this = vueThis()$this.$request.post('/url',{})
而且不光如此,你掛載全局變量,想要有類型推導(dǎo),你還要在vue-runtime-core.d.ts把類型告訴人家,才好用。特別不優(yōu)雅。
import request from '@host/request'declare module '@vue/runtime-core' { interface ComponentCustomProperties { $request: typeof request }}
所以依舊建議使用組合式函數(shù)進(jìn)行封裝,清晰又明了。
// useRequest.tsexport default () => { const get = async (uri: string, params: any = {}) => { return await Request.get(uri, params) } const post = async (uri: string, params: any = {}) => { return await Request.post(uri, params) } return { get, post, }}
// 使用時(shí)import useRequest from './useRequest'const { post } = useRequest()post('/url',{})
一個(gè)use只辦一件事
Vue2始終是以頁(yè)面為單位進(jìn)行思考的,即一個(gè)vue只辦一件事,至于提供的mixin也好,props也好,emit也好,都是為了服務(wù)這個(gè)vue本身的,所有也是為什么簡(jiǎn)單頁(yè)面vue最好用。
但是伴隨著一個(gè)vue的功能越來(lái)越多,代碼也就越來(lái)越復(fù)雜,就變成了左圖Options API的樣子,再加上全局屬性的亂加,mixins的亂用,組件的亂引,整體也變得越來(lái)越冗余,最終變成了大家吐槽的對(duì)象。
Vue3推出的組合式函數(shù)的概念,借鑒了React Hooks的寫法,將原本一個(gè)vue一件事抽象成一個(gè)vue幾件事,再用函數(shù)進(jìn)行打包。最終就是Composition API的樣子。所以我們開(kāi)發(fā)時(shí)就應(yīng)該順應(yīng)Vue3的思維:「一個(gè)use只辦一件事」。