尤大都說Vue3 + script setup + TS + Volar真香,你說香不香?
前兩天尤大官宣發(fā)布了Vue3.2,支持了好幾個很不錯的新特性,而且自信放話:<script setup> + TS + Volar = 真香,是時候了
想看Vue3.2更新的內(nèi)容文檔的讀者可以滑到文章底部點擊「閱讀原文」

相信你已經(jīng)開始使用或者迫不及待地想嘗試Vue3.2了,其實我群里的小伙伴早已經(jīng)開始使用,而且踩了很多坑了,今天給大家分享一下他的實踐投稿文章,希望大家多多支持!
正文如下
目前setup sugar已經(jīng)進行了定稿,而vue3 + setup sugar + TS的寫法看起來很香,所以我大膽嘗試了下,期間發(fā)現(xiàn)一些小問題,分享下我的經(jīng)驗,如有問題,歡迎斧正。
作者:qiuliang
鏈接:https://juejin.cn/post/6990682369992704007
前期準備
使用
vue-cli創(chuàng)建一個 vue3 + TS 的項目相關(guān)可去 cli.vuejs.org/zh/guide/[1] 查看
vscode禁用Vetur,下載Volar
相關(guān)下載以及說明可去 marketplace.visualstudio.com/items?itemN…[2] 查看
注意:vue3 不支持 ie 全系(包括 ie11),因為 ie 并不支持 proxy api。若你想使用 vue3 相關(guān)語法,請等待 vue2.7。預(yù)期將在 2021 q3 或者 q4 發(fā)布 vue2.7,詳情見:github.com/vuejs/rfcs/…[3]
什么是 setup sugar
在單文件組件(SFC)中引入一個新的 <script> 類型 setup。它向模板公開了所有的頂層綁定。
未使用setup sugar
<template>
<Foo :count="count" @click="inc" />
</template>
<script>
import Foo from './Foo.vue'
import { ref } from 'vue'
export default {
setup() {
const count = ref(1)
const inc = () => { count.value++ }
return {
Foo,
count,
inc
}
}
}
</script>
使用了setup sugar
<template>
<Foo :count="count" @click="inc" />
</template>
<script setup>
// 導(dǎo)入的組件也可直接可用于模板(指令同理,同樣會被自動注冊)
import Foo from './Foo.vue'
import { ref } from 'vue'
// 書寫組合式 api 就像在正常的 setup 中一般,但是不需要進行手動地進行 return
const count = ref(0)
const inc = () => { count.value++ }
</script>
你可以在 github.com/vuejs/rfcs/…[4] 以及github.com/vuejs/rfcs/…[5] 看到有關(guān)
setup sugar和ref sugar有關(guān)更多的討論。個人不喜歡ref sugar,個人認為.value的寫法比較好理解,結(jié)合Volar的幫助提示,已經(jīng)沒有多少的心智負擔。
vue3 組合式寫法是否會產(chǎn)生更多問題
目前網(wǎng)絡(luò)上很多人反應(yīng)使用組合式 api 反而顯得代碼更加亂了。那么從vue2的options api到vue3的composition api寫法,究竟會獲得什么好處呢?
邏輯耦合度更高:在 options api中如何一個功能我們需要用到data+method+watch...等更多 api,一段代碼無法合并在一起,我們在閱讀一段邏輯需要進行反復(fù)上下移動進行觀看。而composition api就解決了這個問題。功能抽離:得益于函數(shù)式編程,一個功能邏輯我們可以封裝到一個 hook 中,我們直接導(dǎo)入hook,運行方法,即可。
缺點:從options api切換到composition api最大的問題無異于最大的問題就是沒有強制的代碼分區(qū),如果書寫的人沒有很好的代碼習(xí)慣,那么后續(xù)的人將會看的十分難受。目前我是這么解決的:
自我代碼分區(qū)并且盡量抽離方法(寫好注釋),分區(qū)如下: 相關(guān)引入 響應(yīng)式數(shù)據(jù)、props、emit 定義 生命周期以及 watch 書寫 方法定義 方法、屬性暴露 組件抽離:將頁面拆成兩個文件夾,一個為 views,一個為components。views 和 components 文件夾下有各自的文件。views 文件夾中為頁面入口,掌管數(shù)據(jù),而 components 則為頁面中一些組件抽離。如果是公共組件,再抽離到 components 文件夾下其他位置。hook 抽離:盡可能將邏輯抽離,并不一定要進行復(fù)用。
setup 衍生出的新的 api
define 編譯器宏(compiler macros )
以 define開頭的 api 都為編譯器宏(compiler macros )api,只能在 <script setup> 中使用。它們不需要被導(dǎo)入,并且在處理 <script setup> 時被編譯掉。
注意:
define類 api 必須直接在 setup 中外層進行使用,你無法將其放在方法中。
注意:
define類 api 雖然不用導(dǎo)入,但是這一點和 TS 兼容不太好,如果不引入會提示undefined,如果進行了引入,會有warning提示。
注意:
define類 api雖然目前都可以使用 TS 類型聲明,但是你無法導(dǎo)入一個 interface 或者 type 進行類型聲明(直接在文件中聲明是可以的),因為這樣會報錯。猜測這一點是和define編譯器宏有關(guān),可能后期會被修復(fù)。
defineProps:這個 api 很好理解,就是定義 props 相關(guān)信息。基礎(chǔ)用法:
defineProps({
name: {
type: String,
required: false,
default: 'Petter',
},
userInfo: Object,
tags: Array,
})
使用 TS 類型聲明:
const props = defineProps<{
foo: string
bar?: number
}>()
注:兩個寫法不可以一起進行使用,也就是一個
defineProps不能既使用 TS 類型聲明,也使用基礎(chǔ)用法。
withDefaults:這個方法并非屬于編譯器宏(compiler macros )api,但是這個 api 由defineProps衍生而出。在 TS 類型聲明下無法進行設(shè)置默認值,這個 api 主要是為了解決這個場景。
withDefaults(defineProps<{
size?: number
labels?: string[]
}>(), {
size: 3,
labels: () => ['default label']
})
defineEmits:這個 api 也很好理解,就是定義 emits 相關(guān)信息。不過使用和以前有點不一樣。基礎(chǔ)使用:
// 聲明
const emits = defineEmits(['change', 'delete'])
// 使用
emits('change')
TS聲明類型:
// 聲明
const emit = defineEmits<{ (e: 'change', id: number): void (e: 'update', value: string): void }>()
// 使用
emits('change',1)
defineExpose:在傳統(tǒng)的 Vue 組件中,所有暴露在模板上的東西都隱含地暴露在組件實例上,也就是父組件可以通過ref 或者子鏈可以全量獲取到子組件所有的屬性、方法。大多數(shù)時候,這種全量暴露是過度的,而 vue3 setup 中必須進行手動暴露。
const a = 1
const b = ref(2)
defineExpose({ a, b, })
注意:目前發(fā)現(xiàn)
defineExpose暴露出去的屬性以及方法都是unknown類型,如果有修正類型的方法,歡迎評論區(qū)補充。
hook api
注:useContext API 被棄用,取而代之的是更加細分的 api。
useAttrs:見名知意,這是用來獲取 attrs 數(shù)據(jù),但是這和 vue2 不同,里面包含了class、屬性、方法。在 vue2 中封裝組件透傳屬性、方法你可能這么寫:
<component v-bind='$attrs',v-on='$listeners'></component>
vue3 里刪除了 $listeners,新寫法:
<template>
<component v-bind='attrs'></component>
</template>
<srcipt setup lang='ts'>
const attrs = useAttrs();
<script>
useCSSModule:CSS Modules 是一種 CSS 的模塊化和組合系統(tǒng)。vue-loader 集成 CSS Modules,可以作為模擬 scoped CSS。允許在單個文件組件的setup中訪問CSS模塊。此 api 本人用的比較少,不過多做介紹。useSlots: 顧名思義,獲取插槽數(shù)據(jù)。useCssVars: 此 api 暫時資料比較少。介紹v-bind in styles時提到過。
詳情請見:github.com/vuejs/rfcs/…[6]
useTransitionState: 此 api 暫時資料比較少。useSSRContext: 此 api 暫時資料比較少。
setup 目前存在的限制
修改選項配置需要單開一個 script
配置項的缺失,有時候我們需要更改組件選項,在setup中我們目前是無法做到的。我們需要在上方再引入一個 script,在上方寫入對應(yīng)的 export即可。
<script>
export default {
name: 'YourName',
inheritAttrs: false,
customOptions: {},
}
</script>
<script setup>
// your code
</script>
注意:Vue 3 SFC 一般會自動從組件的文件名推斷出組件的 name。在大多數(shù)情況下,不需要明確的 name 聲明。唯一需要的情況是當你需要
<keep-alive>包含或排除或直接檢查組件的選項時,你需要這個名字。
與 TS 與 ESLint 不是完美融合
與
@typescript-eslint/no-unused-vars規(guī)則不兼容,此規(guī)則含義為定義了,未進行使用。該規(guī)則其實影響不大,關(guān)閉即可。與導(dǎo)入的類型聲明不兼容,當你通過解構(gòu)的方式去導(dǎo)入類型,
setup sugar會進行自動導(dǎo)出。這時候,你就會收到 TS 的一條報錯:此為類型,但被當作值使用。解決辦法:類型導(dǎo)出使用export default導(dǎo)出或者引入時使用import * as xx來進行引入。感謝評論區(qū)補充:
import type { test } from "./test";如此書寫,也是可以解決的。關(guān)于這一點,也許你會想著在上方寫一個 script 進行導(dǎo)入,但是這是不行的。在上方 script 導(dǎo)入的東西,也會被自動導(dǎo)出。詳情見:github.com/vuejs/vue-n…[7]
必須安裝新的插件
集成開發(fā)環(huán)境需要為這個新的 <script setup> 模型提供專門的處理,以便提供模板表達式類型檢查 / 道具驗證等。安裝Volar也是為了這個目的。
vue3 中如何實現(xiàn)國際化
下載最新的
vue-i18n,當前版本"vue-i18n": "^9.1.7"。src 下創(chuàng)建文件夾
locales,創(chuàng)建好對應(yīng)語言文案(這里最好使用 json)。
import en from "./en.json";
import zhHans from "./zh-cn.json";
import zhHant from "./zh-hk.json";
import { createI18n } from "vue-i18n";
import { judgeLang } from "@/utils/url";
const messages = {
en: en,
"zh-hans": zhHans,
"zh-hant": zhHant,
};
const i18n = createI18n({
locale: judgeLang(),
messages,
});
export default i18n;
main.ts中引入i18n
import { createApp } from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import i18n from "@/locales/index";
createApp(App).use(store).use(router).use(i18n).mount("#app");
創(chuàng)建一個 i18n hook
import { provide, inject } from "vue";
import { useI18n } from "vue-i18n";
export function useProvideI18n(): void {
const { t } = useI18n();
// 注入翻譯函數(shù)
provide("t", t);
}
export function useInjectI18n(): (text: string) => string {
const t = inject("t") as (text: string) => string;
return t;
}
在
app.vue,進行注入。隨后,便可在各處進行接收使用。下載
i18n Ally,可查看實時翻譯(強烈推薦)。
vue-i18n 詳情請見:kazupon.github.io/vue-i18n/zh…[8] i18n Ally 插件詳情請見:github.com/lokalise/i1…[9]
vue3.2新增有意思的東西
v-memo(Vue3.2 新增)
記錄指令下的模板樹。該指令期望一個數(shù)組,如果數(shù)組內(nèi)的值,都沒有發(fā)生更新,那么指令下的模板樹也不會發(fā)生更新。
<div v-memo="[valueA, valueB]">
...
</div>
當組件重新渲染時,如果
valueA和valueB保持不變,<div>則將跳過此組件及其子組件的所有更新。實際上,即使是 Virtual DOM VNode 創(chuàng)建也將被跳過,因為可以重復(fù)使用子樹的記憶副本。
官網(wǎng)提到v-memo僅用于性能關(guān)鍵場景中的微優(yōu)化,一般使用到的地方應(yīng)該是渲染大型v-for列表(其中length > 1000)。
<div v-for="item in list" :key="item.id" v-memo="[item.id === selected]">
<p>ID: {{ item.id }} - selected: {{ item.id === selected }}</p>
<p>...more child nodes</p>
</div>
v-bindin<style>(實驗性語法)
支持在單文件組件樣式中使用組件狀態(tài)驅(qū)動的CSS變量。其實,關(guān)于這個提案已經(jīng)持續(xù)很久了(換了寫法而已),本質(zhì)上是利用CSS var() 函數(shù)。
<template>
<div class="text">hello</div>
</template>
<script>
export default {
data() {
return {
color: 'red',
font: {
size: '2em',
},
}
},
}
</script>
<style>
.text {
color: v-bind(color);
/* 表達式需用引號括起來 */
font-size: v-bind('font.size');
}
</style>
你可以在這里看見更多有關(guān)信息 github.com/vuejs/rfcs/…[10]
你可能會遇到的問題
移動端調(diào)試工具 vConsole 發(fā)生棧溢出
一般移動端調(diào)試都是使用vConsole輕量好用,但是目前對 Vue3 兼容性不好,使用到一定時間,會出現(xiàn)棧溢出。這里推薦另外一個調(diào)試工具eruda,eruda目前使用起來并沒有大的問題,但是這個插件特別大,500 多 kb 是對于移動端是無法忍受的,即便壓縮后也有 100 多 kb。這里我們只要根據(jù)環(huán)境判斷,利用 script 動態(tài)插入就好了。
export default function debugInit(): void {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "http://cdn.bootcdn.net/ajax/libs/eruda/2.3.3/eruda.js";
document.getElementsByTagName("head")[0].appendChild(script);
script.onload = function () {
window.eruda.init();
};
}

慎用無根標簽的組件
目前 vue3 中我們已經(jīng)不需要強制組件有一個根標簽了,但是有些情況下,表現(xiàn)并不太好。
例如:transition組件目前和此種寫法偶發(fā)出現(xiàn)問題。
你在Vue3的使用中,有遇到什么坑或者經(jīng)驗之談嗎?可以在評論區(qū)留下你寶貴的建議哦~
參考資料
cli.vuejs.org/zh/guide/: https://link.juejin.cn?target=https%3A%2F%2Fcli.vuejs.org%2Fzh%2Fguide%2F
[2]marketplace.visualstudio.com/items?itemN…: https://link.juejin.cn?target=https%3A%2F%2Fmarketplace.visualstudio.com%2Fitems%3FitemName%3Djohnsoncodehk.volar
[3]github.com/vuejs/rfcs/…: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs%2Fdiscussions%2F296
[4]github.com/vuejs/rfcs/…: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs%2Fpull%2F222
[5]github.com/vuejs/rfcs/…: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs%2Fpull%2F227
[6]github.com/vuejs/rfcs/…: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs%2Fblob%2Fmaster%2Factive-rfcs%2F0043-sfc-style-variables.md
[7]github.com/vuejs/vue-n…: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Fvue-next%2Fissues%2F3238
[8]kazupon.github.io/vue-i18n/zh…: https://link.juejin.cn?target=https%3A%2F%2Fkazupon.github.io%2Fvue-i18n%2Fzh%2Fintroduction.html
[9]github.com/lokalise/i1…: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Flokalise%2Fi18n-ally%2Fblob%2Fmaster%2FREADME.md
[10]github.com/vuejs/rfcs/…: https://link.juejin.cn?target=https%3A%2F%2Fgithub.com%2Fvuejs%2Frfcs%2Fblob%2Fmaster%2Factive-rfcs%2F0043-sfc-style-variables.md
