# Vue
视图部分,Vue 需要编写 Template 模板。
- Template 跟 HTML 很接近,上手容易,使用双花括号
{{}}
表示插值表达式,在插值表达式内可以编写 JS 代码。 - 在插值表达式上类型检查、代码补全很坑,Vetur 虽然可以打开 Experimental Interpolation Features 选项,但仍处于实验阶段,打开后会很卡。
逻辑部分,Vue 2.x 采用 Options API,Vue 3.x 引入了 Composition API,此外还有一种 Class Style Component 目前已经被官方废弃。
- Options API 对 TypeScript 类型推断非常不友好
- Composition API 一方面可以实现 AOP 面向切面,这点和 React Hooks 一样;另一方面对 TypeScript 的支持更加友好
Vue 的单文件组件 (SFC) 把 HTML、JS、CSS 都放在同一个文件里面。
- 这样对业务开发人员来说很好用。
- 对周边生态工具的实现者来说很坑,比如:语法高亮、Webpack 打包等等都成了问题。
- 对环境搭建人员来说有一些小坑,还好大多场景都可以交给 Vue CLI 处理,不需要自己配置。
Vue 的优点:
- 动画/过渡支持的很优雅
- VueRouter、Vuex 集成的很好,开箱即用
# Vue 3 中的 TypeScript
本节会说 Vue 3 对 TypeScript 的支持还是不如 React 好,具体会通过以下几点展开讲解:
- Vetur 插件对单 Vue 文件内的 TS 支持很好,但跨 Vue 文件会丢失 TS 类型 (这是 TypeScript 的不足)
- 上一点我们说 TypeScript 的不足是指 TS 不认识 .vue 后缀的文件,TS 也提供了一些技术手段 (即编写 shims-vue.d.ts 文件),然而在实践中还是存在许多缺陷
- 如果我们使用 JSX 编写业务代码,那么就不存在 .vue 后缀的文件了,问题自然就解决了
# Vetur 对 TS 的支持
VSCode 的 Vetur 插件有一个实验特性:templateInterpolationService
,打开之后可以开启类型检查,但还是比较鸡肋 (不能实现跨 Vue 文件的检查)。
单 Vue 文件内工作良好
<template>
<div>
<!-- TS 报错 -->
{{ obj + 1 }}
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
export default defineComponent({
name: 'App',
setup() {
const obj = {
name: 'zhang',
};
return {
obj,
};
},
});
</script>
跨 Vue 文件无法工作
<template>
<div>
<span :style="{ color: color }">{{ msg.text }}</span>
</div>
</template>
<script lang="ts">
import { defineComponent, PropType } from 'vue';
export default defineComponent({
name: 'LogItem',
props: {
msg: {
type: Object as PropType<{
type: 'info' | 'warn' | 'error';
text: string;
}>,
required: true,
},
},
setup(props) {
const colorMapByType = { info: 'gray', warn: 'orange', error: 'red' };
const color = colorMapByType[props.msg.type];
return { color };
},
});
</script>
<template>
<log-item :msg="{ type: 'info', text: 'hello' }"></log-item>
<!-- msg 入参类型错误,无法检查出来 -->
<log-item :msg="'a'"></log-item>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import LogItem from './components/LogItem.vue';
export default defineComponent({
name: 'App',
components: { LogItem },
});
</script>
# shims-vue.d.ts
跨 Vue 文件之所以会丢失 TS 类型,是因为我们 import 'xxx.vue'
的时候,后缀名是 .vue。TypeScript 不知道怎么处理 .vue 后缀的文件,所以 TypeScript 提供了一个解决方法是编写 shims-vue.d.ts 文件 (如下所示)。
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
上面这种写法显然是不够的,*.vue
是对所有 vue 文件生效的。
# 在 Vue 中使用 JSX
Vue 有 template、JSX、render 函数这 3 种方式编写视图,前两种最终都会编译成 render 函数。
和 React 一样,JSX 的编译需要依赖 Babel 插件,Vue 3 可以使用 jsx-next (opens new window) 实现,Vue 2 可以使用 jsx (opens new window) 实现。
而 JSX 的类型检查需要依赖 TypeScript 官方支持,和 Babel 不同 TypeScript 不支持插件扩展。React JSX 得到了 TypeScript 官方支持,然而 Vue 2 JSX 没有,二者有着一些语法区别。Vue 3 调整了 JSX 的语法并和 React 保持统一,这样 Vue 中的 JSX 也能得到 TypeScript 支持了。
# 对 props 入参的 TS 检查
业务代码调用组件库,为了考察 props 对 TS 的支持,我们做 4 个实验:
- 都采用 SFC + TS 编写,无法对 props 做类型检查
- 业务代码采用 SFC + TS,组件库采用 TSX,无法对 props 做类型检查
- 业务代码采用 TSX,组件库采用 SFC + TS,VSCode 不会报错,但编译时控制台会报类型错误
- 都采用 TSX,VSCode 在代码编写阶段就能够报类型错误,编译时控制台也会有错误信息
# 对 Vue 进行测试
# SFC
import { mount } from '@vue/test-utils'
import Modal from '../Modal.vue'
test('Modal', () => {
const wrapper = mount(Modal)
expect(wrapper.find('div.a-class').exists()).toBe(true)
})
# Snapshot Test
可以确保 DOM 结构不会被增量代码破坏。
# RFC 解读
Vue 的 RFC 仓库 (opens new window) 反映了 Vue 未来的发展,RFC 现在有 30 多个文档,文件名类似下面这样:
- 0001-new-slot-syntax.md
- 0002-slot-syntax-shorthand.md
# 0001-new-slot-syntax (opens new window)
slot 的 API 发生了变化,因为旧的 API 不是很自然。
old style | new style | |
---|---|---|
插槽 (Slots) | <foo v-slot></foo> | |
具名插槽 (Named Slots) | <foo v-slot:name></foo> | |
作用域插槽 (Scoped Slots) | 一句话说不清 | <foo v-slot="xxx"></foo> |
v-slot 语法糖 | 无 | <foo #name></foo> |
定义插槽 | <div><slot name="..." :arg="..."></slot></div> |
# 0006-slots-unification (opens new window)
# 0004-global-api-treeshaking (opens new window)
# Tree Shaking 减小体积
Vue 3 只能使用 named imports 导包,代码如下,好处是可以让打包工具的 Tree Shaking 发挥作用,减小最终生成的 bundle 体积。
import { nextTick, observable } from 'vue'
nextTick(() => {})
const obj = observable({})
# 对多 App 实例的支持更好
在 Vue 2 中,业务代码使用 new Vue()
来创建 App。插件 (Plugin) (opens new window) 会往 Vue
上 install 函数,代码如下。这样多个 App 实例共享同一个 Vue
原型可能会有插件冲突。
const plugin = {
install: Vue => {
Vue.nextTick(() => {
// ...
})
}
}
在 Vue 3 中,业务代码使用 Vue.createApp
来创建 App,插件往 app 实例上 install 函数,代码如下。这样不同 App 就可以隔离开。
import { nextTick } from 'vue'
const plugin = {
install: app => {
nextTick(() => {
// ...
})
}
}
# 0025-teleport (opens new window)
# 0013-composition-api (opens new window)
# Vue 杂谈
# Vue 不适合大型项目吗?
我个人认为相比 React 来说,Vue 不适合大型项目,除非使用 Vue 3 + TSX。原因有几个:
- 可重构性差,组件修改 props、$emit 名字的时候,其它调用方不会在编译时报任何错误,甚至有些在运行时也不会报错;VSCode 没办法对 .vue 文件进行重构;
- TS 类型推断功能弱,一旦类型走到 Template 里面,TS 类型推断就会失效;
- 缺乏组件声明功能,当你想调用他人封装的组件的时候,你甚至不知道组件的入参 (props) 和返回值 ($emit) 是什么,你必须点开其源码仔细阅读才明白;props 会声明在 options 里面,这很好;但 $emit 会散落在组件源码的各个角落;虽然 Vue 3 也可以将 $emit 声明在 options 里面,但在 VSCode 中,调用方代码还是没有代码补全;