尤大都说Vue3 + script setup + TS + Volar真香,你说香不香?
前两天尤大官宣发布了Vue3.2,支持了好几个很不错的新特性,而且自信放话:<script setup> + TS + Volar = 真香,是时候了
想看Vue3.2更新的内容文档的读者可以滑到文章底部点击「阅读原文」
相信你已经开始使用或者迫不及待地想尝试Vue3.2了,其实我群里的小伙伴早已经开始使用,而且踩了很多坑了,今天给大家分享一下他的实践投稿文章,希望大家多多支持!
正文如下
目前setup sugar
已经进行了定稿,而vue3 + setup sugar + TS
的写法看起来很香,所以我大胆尝试了下,期间发现一些小问题,分享下我的经验,如有问题,欢迎斧正。
作者:qiuliang
链接:https://juejin.cn/post/6990682369992704007
前期准备
使用
vue-cli
创建一个 vue3 + TS 的项目相关可去 cli.vuejs.org/zh/guide/[1] 查看
vscode
禁用Vetur
,下载Volar
相关下载以及说明可去 marketplace.visualstudio.com/items?itemN…[2] 查看
注意:vue3 不支持 ie 全系(包括 ie11),因为 ie 并不支持 proxy api。若你想使用 vue3 相关语法,请等待 vue2.7。预期将在 2021 q3 或者 q4 发布 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>
// 导入的组件也可直接可用于模板(指令同理,同样会被自动注册)
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] 看到有关
setup sugar
和ref sugar
有关更多的讨论。个人不喜欢ref sugar
,个人认为.value
的写法比较好理解,结合Volar
的帮助提示,已经没有多少的心智负担。
vue3 组合式写法是否会产生更多问题
目前网络上很多人反应使用组合式 api 反而显得代码更加乱了。那么从vue2
的options api
到vue3
的composition api
写法,究竟会获得什么好处呢?
逻辑耦合度更高:在 options api
中如何一个功能我们需要用到data
+method
+watch
...等更多 api,一段代码无法合并在一起,我们在阅读一段逻辑需要进行反复上下移动进行观看。而composition api
就解决了这个问题。功能抽离:得益于函数式编程,一个功能逻辑我们可以封装到一个 hook 中,我们直接导入hook,运行方法,即可。
缺点:从options api
切换到composition api
最大的问题无异于最大的问题就是没有强制的代码分区,如果书写的人没有很好的代码习惯,那么后续的人将会看的十分难受。目前我是这么解决的:
自我代码分区并且尽量抽离方法(写好注释),分区如下: 相关引入 响应式数据、props、emit 定义 生命周期以及 watch 书写 方法定义 方法、属性暴露 组件抽离:将页面拆成两个文件夹,一个为 views
,一个为components
。views 和 components 文件夹下有各自的文件。views 文件夹中为页面入口,掌管数据,而 components 则为页面中一些组件抽离。如果是公共组件,再抽离到 components 文件夹下其他位置。hook 抽离:尽可能将逻辑抽离,并不一定要进行复用。
setup 衍生出的新的 api
define 编译器宏(compiler macros )
以 define
开头的 api 都为编译器宏(compiler macros )api,只能在 <script setup>
中使用。它们不需要被导入,并且在处理 <script setup>
时被编译掉。
注意:
define
类 api 必须直接在 setup 中外层进行使用,你无法将其放在方法中。
注意:
define
类 api 虽然不用导入,但是这一点和 TS 兼容不太好,如果不引入会提示undefined
,如果进行了引入,会有warning
提示。
注意:
define
类 api虽然目前都可以使用 TS 类型声明,但是你无法导入一个 interface 或者 type 进行类型声明(直接在文件中声明是可以的),因为这样会报错。猜测这一点是和define
编译器宏有关,可能后期会被修复。
defineProps
:这个 api 很好理解,就是定义 props 相关信息。基础用法:
defineProps({
name: {
type: String,
required: false,
default: 'Petter',
},
userInfo: Object,
tags: Array,
})
使用 TS 类型声明:
const props = defineProps<{
foo: string
bar?: number
}>()
注:两个写法不可以一起进行使用,也就是一个
defineProps
不能既使用 TS 类型声明,也使用基础用法。
withDefaults
:这个方法并非属于编译器宏(compiler macros )api,但是这个 api 由defineProps
衍生而出。在 TS 类型声明下无法进行设置默认值,这个 api 主要是为了解决这个场景。
withDefaults(defineProps<{
size?: number
labels?: string[]
}>(), {
size: 3,
labels: () => ['default label']
})
defineEmits
:这个 api 也很好理解,就是定义 emits 相关信息。不过使用和以前有点不一样。基础使用:
// 声明
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
:在传统的 Vue 组件中,所有暴露在模板上的东西都隐含地暴露在组件实例上,也就是父组件可以通过ref 或者子链可以全量获取到子组件所有的属性、方法。大多数时候,这种全量暴露是过度的,而 vue3 setup 中必须进行手动暴露。
const a = 1
const b = ref(2)
defineExpose({ a, b, })
注意:目前发现
defineExpose
暴露出去的属性以及方法都是unknown
类型,如果有修正类型的方法,欢迎评论区补充。
hook api
注:useContext API 被弃用,取而代之的是更加细分的 api。
useAttrs
:见名知意,这是用来获取 attrs 数据,但是这和 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 的模块化和组合系统。vue-loader 集成 CSS Modules,可以作为模拟 scoped CSS。允许在单个文件组件的setup
中访问CSS模块。此 api 本人用的比较少,不过多做介绍。useSlots
: 顾名思义,获取插槽数据。useCssVars
: 此 api 暂时资料比较少。介绍v-bind in styles
时提到过。
详情请见:github.com/vuejs/rfcs/…[6]
useTransitionState
: 此 api 暂时资料比较少。useSSRContext
: 此 api 暂时资料比较少。
setup 目前存在的限制
修改选项配置需要单开一个 script
配置项的缺失,有时候我们需要更改组件选项,在setup
中我们目前是无法做到的。我们需要在上方
再引入一个 script
,在上方写入对应的 export
即可。
<script>
export default {
name: 'YourName',
inheritAttrs: false,
customOptions: {},
}
</script>
<script setup>
// your code
</script>
注意:Vue 3 SFC 一般会自动从组件的文件名推断出组件的 name。在大多数情况下,不需要明确的 name 声明。唯一需要的情况是当你需要
<keep-alive>
包含或排除或直接检查组件的选项时,你需要这个名字。
与 TS 与 ESLint 不是完美融合
与
@typescript-eslint/no-unused-vars
规则不兼容,此规则含义为定义了,未进行使用。该规则其实影响不大,关闭即可。与导入的类型声明不兼容,当你通过解构的方式去导入类型,
setup sugar
会进行自动导出。这时候,你就会收到 TS 的一条报错:此为类型,但被当作值使用。解决办法:类型导出使用export default
导出或者引入时使用import * as xx
来进行引入。感谢评论区补充:
import type { test } from "./test";
如此书写,也是可以解决的。关于这一点,也许你会想着在上方写一个 script 进行导入,但是这是不行的。在上方 script 导入的东西,也会被自动导出。详情见:github.com/vuejs/vue-n…[7]
必须安装新的插件
集成开发环境需要为这个新的 <script setup>
模型提供专门的处理,以便提供模板表达式类型检查 / 道具验证等。安装Volar
也是为了这个目的。
vue3 中如何实现国际化
下载最新的
vue-i18n
,当前版本"vue-i18n": "^9.1.7"
。src 下创建文件夹
locales
,创建好对应语言文案(这里最好使用 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");
创建一个 i18n hook
import { provide, inject } from "vue";
import { useI18n } from "vue-i18n";
export function useProvideI18n(): void {
const { t } = useI18n();
// 注入翻译函数
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 新增)
记录指令下的模板树。该指令期望一个数组,如果数组内的值,都没有发生更新,那么指令下的模板树也不会发生更新。
<div v-memo="[valueA, valueB]">
...
</div>
当组件重新渲染时,如果
valueA
和valueB
保持不变,<div>
则将跳过此组件及其子组件的所有更新。实际上,即使是 Virtual DOM VNode 创建也将被跳过,因为可以重复使用子树的记忆副本。
官网提到v-memo
仅用于性能关键场景中的微优化,一般使用到的地方应该是渲染大型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-bind
in<style>
(实验性语法)
支持在单文件组件样式中使用组件状态驱动的CSS变量。其实,关于这个提案已经持续很久了(换了写法而已),本质上是利用CSS var() 函数
。
<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>
你可以在这里看见更多有关信息 github.com/vuejs/rfcs/…[10]
你可能会遇到的问题
移动端调试工具 vConsole
发生栈溢出
一般移动端调试都是使用vConsole
轻量好用,但是目前对 Vue3 兼容性不好,使用到一定时间,会出现栈溢出。这里推荐另外一个调试工具eruda
,eruda
目前使用起来并没有大的问题,但是这个插件特别大,500 多 kb 是对于移动端是无法忍受的,即便压缩后也有 100 多 kb。这里我们只要根据环境判断,利用 script 动态插入就好了。
export default function debugInit(): void {
const script = document.createElement("script");
script.type = "text/javascript";
script.src = "//cdn.bootcdn.net/ajax/libs/eruda/2.3.3/eruda.js";
document.getElementsByTagName("head")[0].appendChild(script);
script.onload = function () {
window.eruda.init();
};
}
慎用无根标签的组件
目前 vue3 中我们已经不需要强制组件有一个根标签了,但是有些情况下,表现并不太好。
例如:transition
组件目前和此种写法偶发出现问题。
你在Vue3的使用中,有遇到什么坑或者经验之谈吗?可以在评论区留下你宝贵的建议哦~
参考资料
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