前言
Vue3已經發布很長一段時間了,雖然早就用上了框架,但是很多人依舊保持著Vue2的思維習慣,導致大家在實際開發中并沒有感覺到提升,屬實是新瓶裝舊酒。我們應該意識到這并不僅僅是一個數字大版本的迭代,而是一次全新的開發體驗。
讓我們一起看看在使用Vue3開發時,應該在哪些地方做出改變?
正文
使用<script setup>
如果是從Vue2轉到Vue3,我們很熟悉的一種寫法是選項式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>
通過export default
導出一個對象,內部的data
,methods
,watch
都可以使用,this
依然可以保留,并指向vue
,setup
中如果想用props
、emit
等,通過參數傳遞。在setup
中可以直接使用新寫法,組件通過components
進行引入。可以極大的還原Vue2的用法,如果團隊的組件庫是使用Vue2寫的,可以用很小的成本就改造完成。
為了更好的類型推導,vue
還提供了defineComponent
方法。
export default defineComponent({
components: { CompA },
setup (props, context) {
// do something
},
})
但其實官方并不推薦這種寫法,這種寫法僅僅是為了兼容舊代碼,這也是你感覺Vue3沒有提升的很大一方面因素。就像是iPhone更新,當外觀有變化時你才會覺得是大更新,系統升級個IOS18,你覺得卵用沒用。所以更好的方式應該是<script setup>
標簽對的寫法。
<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>
你會發現有很多核心的變化,首先不再需要export
導出了,標簽對內直接就是一個setup
環境。ref
可以直接寫,也沒有了methods
,你寫一個就是一個方法,直接就可以綁定。為什么呢?官方不是說所有的值都需要return
出去嗎?放心,@vue/compiler-sfc
幫你解決了這些煩惱。
其次這種寫法是去this
化的,比如以往我們調用router
都是this.$router
這么使用,而現在你需要引入useRouter
,可以更好的分辨來源。對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>
同名簡寫
以往我們綁定一個值需要這樣:
<template>
<div :id="id">
<Comp :title="title" />
</div>
</template>
<script>
export default {
data () {
return {
id: 'container',
title: '標題',
}
},
}
</script>
而現在變得極其簡單,尤其是Vue升級到v3.4.x
以上之后,因為它增加了同名簡寫。
<script setup lang="ts">
const id = ref('container')
const title = ref('標題')
</script>
<template>
<div :id>
<CompA :title />
</div>
</template>
怎么樣,有沒有覺得非常優雅,不過比較可惜的是這個寫法esLint
目前還不支持,會報異常,需要在.eslintrc
中忽略一下。
"rules": {
"vue/valid-v-bind": "off"
}
拒絕mixins
我們之前Vue2的模版中有很多的mixins,而且不乏有全局引入的mixins,在遷移模板時,也需要一起處理,我看到官方也有案例,有mixins
的,還有extends
的。
const mixin = {
created() { console.log(1) }
}
createApp({
created() { console.log(2) },
mixins: [mixin]
})
但是均都對組合式API不友好,因為mixin
內部有不少調用this
內部環境的地方,很難在<script setup>
中使用,而且mixins
最大的問題就是,你無法溯源,別人在某個犄角旮旯引入一個全局mixins
,你根本找不到,而且也對類型推導極其不友好。所以建議使用組合式函數代替。比如我們之前有一個NavBar
的mixins
,里面處理了很多邏輯,就可以用組合式函數進行封裝。
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
在使用時引用進來即可,而且只要你調用了useNavbar
,內部的onMounted
也會執行,非常方便。
import useNavbar from './useNavbar'
const { navbarProps, setNavbar} = useNavbar()
減少全局變量
之前在Vue2時,我們經常會將一些常用屬性掛載在Vue.prototype
原型上,方便內部用this.xxx
使用。比如我們會把Request
掛載上去,Vue.prototype.$request = Request
。我們發送請求時直接this.$request
即可,很方便。其實很多Vue2的依賴庫都是這么寫的,比如vue-router
,就是在install
中將$router
寫為了全局變量,在我們使用Vue.use(Router)
后,方便我們使用。
而在Vue3中也有替代方案,app.config.globalProperties.$request = Request
。但是在使用時就比較麻煩了,因為沒有this
環境,需要從實例上取。
import { getCurrentInstance } from 'vue'
const $this = getCurrentInstance()?.appContext.config.globalProperties
$this?.$request.post('/url', {})
很深的API,既然這么深,我想我封裝一下吧。
// vueThis.ts
import { getCurrentInstance } from 'vue'
export default getCurrentInstance()?.appContext.config.globalProperties
// 使用時
import vueThis from './vueThis'
vueThis.$request.post('/url',{})
但是你說氣人不,getCurrentInstance
還要識別調用的時機,你直接賦值,相當于引入時就運行了,這個時候還沒實例,你還得閉包包一下,調用也不好看。
// vueThis.ts
export default () => {
return getCurrentInstance()?.appContext.config.globalProperties
}
// 使用時
import vueThis from './vueThis'
const $this = vueThis()
$this.$request.post('/url',{})
而且不光如此,你掛載全局變量,想要有類型推導,你還要在vue-runtime-core.d.ts
把類型告訴人家,才好用。特別不優雅。
import request from '@host/request'
declare module '@vue/runtime-core' {
interface ComponentCustomProperties {
$request: typeof request
}
}
所以依舊建議使用組合式函數進行封裝,清晰又明了。
// useRequest.ts
export 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,
}
}
// 使用時
import useRequest from './useRequest'
const { post } = useRequest()
post('/url',{})
一個use只辦一件事
Vue2始終是以頁面為單位進行思考的,即一個vue只辦一件事,至于提供的mixin
也好,props
也好,emit
也好,都是為了服務這個vue本身的,所有也是為什么簡單頁面vue最好用。
但是伴隨著一個vue的功能越來越多,代碼也就越來越復雜,就變成了左圖Options API
的樣子,再加上全局屬性的亂加,mixins的亂用,組件的亂引,整體也變得越來越冗余,最終變成了大家吐槽的對象。
Vue3推出的組合式函數的概念,借鑒了React Hooks
的寫法,將原本一個vue一件事抽象成一個vue幾件事,再用函數進行打包。最終就是Composition API
的樣子。所以我們開發時就應該順應Vue3的思維:「一個use只辦一件事」。