前言
这两天将一个 vite+js 的项目用 vue-cli+ts 重构了一遍
先说背景,这是学校课程的一个项目,团队基本都是小白,学习带编写,持续了一年
再说项目结构,我习惯 一个文件只默认导出一个函数/数据,在模块的 index 文件中合并导出,所有文件都从模块中间件导入数据
这个项目功能还是蛮复杂的,分了百来个文件,只能一个个修改,也是第一次用 ts 写大项目,积累了一些 ts 的经验,总结分享一下
为什么
为什么选 vue-cli
这个项目开展的时候,当时 vite2 刚出,想用个比较新的工具
而且 vite 的启动速度确实比 vue-cli 快,也就选择了 vite
然而,项目大了之后,vite 的热更新非常慢
也可能是因为我项目结构的问题,js 文件彼此间的引用较为复杂,我在 js 文件中保存一下,热更新时页面会自动刷新,加载 3-5 秒才能看到效果
把项目复制到 vue-cli 中,修改同一处文件,页面不需要刷新,瞬间出结果
至于为什么会这样,我没学过二者的原理,不太了解,就我感受而言,vite 的热更新是真的拉跨,所以选择了 vue-cli
为什么选 ts
本来项目开展时就打算用 ts 的,但照顾到其他零基础的成员,就没用
现在项目验收结束了,就用 ts 重构一遍吧
最近 ts 比较热门,类型推断和参数限定也是很有用的
接下来聊聊重构期间用到的一些较为重要的知识吧
断言
类型断言
ts 中有两种类型断言方式,前置断言 <> 和 后置断言 as
const obj: { a?: number } = {}
Math.abs(<number>obj.a)
Math.abs(obj.a as number)
因为 obj.a 可能为空,直接操作会报错,如果确定调用时已经赋值,可以断言为所需的类型
非空断言
上面哪个例子,也可以通过非空断言来实现
一种方式是用 if 包裹
const obj: { a?: number } = {}
const a = obj.a
if (a) {
Math.abs(a)
}
也可以后置 ! 来进行非空断言
const obj: { a?: number } = {}
Math.abs(obj.a!)
在赋值时断言
看看下面这个例子
const canvasData: CanvasData = {
canvas: undefined,
}
interface CanvasData {
canvas?: HTMLCanvasElement
}
// 每次使用都需要非空断言
canvasData.canvas!.width
canvasData.canvas!.height
// 页面挂载就赋值
onMounted(() => {
const vm = getCurrentInstance()
canvasData.canvas = vm!.refs.canvas as HTMLCanvasElement
})
要存储 canvas 元素,因为在设置初值时只能为 undefined,那么接口类型就会包含 undefined,在使用时也需要非空断言,较为麻烦
可以采取下面的方式
const canvasData: CanvasData = {
canvas: <HTMLCanvasElement>(<any>undefined),
}
interface CanvasData {
canvas: HTMLCanvasElement
}
// 使用不需要非空断言
canvasData.canvas.width
canvasData.canvas.height
any 和 unknown 都表示任意类型,也可以用于断言任何类型,通过两次断言,将 undefined 作为 HTMLCanvasElement 类型赋值
函数
获取返回值类型
可以通过 ReturnType 获取一个函数的类型
在项目中有一个函数,其返回值过于复杂,不想专门写接口,就可以使用 ReturnType<typeof fun> 获取该函数的返回值并取别名
// 获取数据,返回类型非常复杂
function getData(num: number) {
if (num > 1) {
return {
sign: true,
a: 1,
b: 1,
……
}
} else {
return {
sign: false,
a: 0,
bb: 1,
……
}
}
}
// 获取函数返回类型并取别名
type DataType = ReturnType<typeof getData>
function useData(data: DataType) {
// 根据标识区分使用哪类数据
if (data.sign) {
data.b
} else {
data.bb
}
}
重载
当一个函数根据参数区别返回值时,可以使用重载
下面这个函数,传入 'bar' 则返回值为数字类型,传入 time 则将数字转换为时间字符串再返回
function getBarOrTime(type: 'bar'): number
function getBarOrTime(type: 'time'): string
function getBarOrTime(type: string) {……}
枚举
我们可以用带名字的常量,清晰的表达意图
之前的js版本
// 模式 1-选择 2-添加 3-裁剪 4-删除
const mode = ref(1)
if (mode.value == 1) {……}
用 ts 的枚举
const mode = ref(Mode.Select)
enum Mode {
Select = 1,
Add,
Tailor,
Delete,
}
if (mode.value == Mode.Select) {……}
在 js 中也可以定义对象来实现和枚举相同的效果,二者的区别在于编码阶段,枚举会被编码成数字,而对象仍会保留
疑问
在重构期间遇到的几个问题,到现在还没有解决
-
*.vue文件的<script>中设置lang="ts",就会直接变为 tsx 环境,前置断言<>将不允许使用 -
vue 不认识
pointer-events,没有被化为 css 属性中,直接赋值给style会报错我的处理方式是转成 any,不知道有没有其他处理方式
<div :style="(styleObj as any)"></div> -
防抖函数的类型怎么写? 我在 2000字助你精通防抖与节流 中实现的防抖函数,在尝试为其写 ts 类型时无法完美书写,参数、返回值、this 总有一个报错,目前只能用 any 解决
对于以上问题,如果掘有解决方案或想法,救救孩子吧
结语
这些就是这次重构时得到的 TS 经验了
如果有所帮助,希望能点赞关注,鼓励一下作者