前言
先前发过一篇对于 shimes-vue-ts 文件的思考,其中有一节写到项目 ts 迁移所遇到的问题和解决方法。因为项目才刚迁移完一部分 ts(用得到的部分),踩坑的过程还在进行中,为了更好的记录 ts 迁移过程中所遇到的问题,还是单独写一篇来做记录吧。此篇便是改进后的问题记录,以及相对详细的解决方法。该篇会持续跟新哦~
问题集锦
动态引入无 ts 声明的文件
项目内有动态设置 cookie 的文件,因为不同测试环境需要配置不同的 cookie,每天都需要从服务器拿取一次 cookie,目前的做法为:
1 | // cookie-set 文件夹已经移除 git 缓存跟踪 |
由于需要迁移 ts,对于原有的 cookie-set.js 需要进行类型声明(或者都改成 cookie-set.ts),为了兼容两种情况,这里还是做了对应类型声明,如:
1 | // 在 declarations.d.ts 文件内添加 |
引入无 typings 的 npm 包
比较全的 npm 包会包含对应的 typings 文件来支持 ts 项目,但是对于没有 typings 文件的包来说,我们需要对齐进行外部文件声明。那么为了支持 vue.use 方法,我们可以这么写对应的声明:
针对默认导出的声明写法:
1 | // 此适用于 import vueClipboard from 'vue-clipboard2'; |
针对解构的写法:
1 | // 此适用于 import { monitorVue } from 'fe-monitor-sdk'; |
export 和 export default 可参考模块部分内容
参考文章: 模块、TypeScript 支持
vue-router 的组建引用报错
虽然在 webpack 内我们配置了 alias,但那仅仅只是 webpack 打包时用的,ts 并不认账,它有自己的配置文件。因此,在升级至 ts 项目时,我们还得为 ts 配置一份模块路径,如:
1 | // tsconfig.json |
此外,因为编辑器的原因使得无法识别 .vue 后缀,所以对于 vue 文件的引用必须添加 .vue 后缀,如:
1 | import myComponent from './my-component.vue'; |
vue 的 data 部分爆红
这个问题比较隐蔽,折腾了很久才发现因为 data 为函数(主要是写惯了才难以察觉,官网文档因为类型推断所以没有写),其内的对象为返回值,因此此部分的声明可以写(个人推荐不要用断言):
1 | // 返回值声明写法 |
注: 主要是为 data 内的数组、对象声明对应类型,都为基础类型时不写后面使用也不会报错。
vue 的 mixins 文件写法
Vue 的 mixins 写法有两种,一种为普通的 ts 写法,另一种为装饰器的写法
1 | // 原来的写法 |
注: 普通 ts 写法的 computed 部分需要添加范围值,可参考 vue 文档 TypeScript 支持部分。
VS Code experimentalDecorators 问题
因为 vue 装饰器写法为实验性特性,可能在未来的发行版中发生变化,因此需要配置此参数来删除警告。直接根据警告来做相应配置,即在 tsconfig.json 内添加属性:
1 | "experimentalDecorators": true |
类的静态方法
关于类一般会采用 abstruct 抽象类来规范方法和属性等类的细节,但是对于“类”中 static 部分无法进行抽象规范,需要在对应静态方法部分进行单独处理,对于此部分是否有更好的处理方法存在疑问 🤔(如:提取一个 interface 之类的声明)。目前想到的比较靠谱的写法有两个:
namespace 写法
官方文档中也有说过,对于业务内的模块来说,推荐使用 namespace 来做全局命名,因此对于业务内比较通用的公共方法来说,可以使用 namespace 来处理。
对于多层命名空间的写法,可用别名写法 import NS = FirstNameSpace.SecondNameSpace
,然后直接通过 NS.xxx
来直接取对应属性即可。同时区别加载模块时使用的 import someModule = require('moduleName')
,此处的别名仅仅只是创建一个别名而已,简化代码量。
module 文件
另一种可用 ES6 的思想,import + export ,因为类中只有 static 方法,因此可以认为该类为一个模块,而一个模块对应一个文件,因此作为一个 ts 文件来存储对应方法,需要时在 import 引入即可。
eslint 迁移至 tslint 时部分校验修复失效问题
semi 无自动补全
将原来配置的 eslint 的 semi 校验替换:
1 | // 原来的配置 |
import 引入没有使用的内容不报错
因为 @vue/eslint-config-typescript 的文件内将 @typescript-eslint/no-unused-vars
给注释掉了,同时 no-unused-vars
设置为 ‘off’,使得对没有使用的变量都不校验。其对应设置的解释为:
传送门
解决方法(跟项目 eslint 和 typescript 版本不同而不同):
1 | '@typescript-eslint/no-unused-vars': [ |
$refs 引用报错问题
由于 Vue 对 refs 的声明为 type Vue | Element | Vue[] | Element[],在通过 $refs 调用对应组件方法时,因为 Vue 和 Element 上没有对应方法声明,因此我么需要对其进行断言处理,如:
1 | interface MyComponent extends Vue { |
@Prop 的类型问题
官方对于 @Prop 装饰器的写法提供了两种思路,分别为:
1 | // 不带 type |
带 type 的和原来的写法可以说没什么区别,但是不带 type 的会出现两种问题:
问题一
当为 Boolean 类型时,会使得属性官方 boolean 的简写方式无法生效,如:
1 | <!-- 不写 type 时此不生效,只能显示绑定 true 值 --> |
问题二
此不会触发部分的相应的 ts 类型校验,例如:
1 | <!-- 假如对应 props 为: @Prop() likes!: number; --> |
个人建议: 除非非常确定传入的 Prop 的类型,否则尽量都加上对应的 type 声明
exceljs 引入报错问题
引入 exceljs
后,ts 一直报错 import("stream")
行因为没有找到 stream 包,因此报错。
问题定位了很久,才发现 stream
是 Node 自带的包,而我们的 tsconfig.json
内却并没有将对应的 node 类型引入,因此会报错,解决方法:
1 | { |
注意,引入 node 后,可能会引发新的 ts 问题,比如自定义的
process.env.XXX
可能为 undefined,这个需要自己做兼容了。
参考 issue
拓展内容
namespace
TS 里的 namespace 主要是解决命名冲突的问题,会在全局生成一个对象,定义在 namespace 内部的类都要通过这个对象的属性访问。对于内部模块来说,尽量使用 namespace 替代 module,可参考 namespace 一节。例如:
1 | namespace Test { |
注:import xx = require(‘xx’) 为加载模块的写法,不要与取别名的写法混淆。默认全局环境的 namespace 为 global
参考文档:namespace
module
模块可理解成 Vue 中的单个 vue 文件,它是以功能为单位进行划分的,一个模块负责一个功能。其与 namespace 的最大区别在于:namespace 是跨文件的,module 是以文件为单位的,一个文件对应一个 module。类比 Java,namespace 就好比 Java 中的包,而 module 则相当于文件。
如果你的模块需要将新的名称引入全局命名空间,那么就应该使用全局声明。如果你的模块无需将新的名称引入全局命名空间,那么就应该使用模块导出声明。