步骤
- 使用 yarn add 安装 @vue/cli-service 对应版本的 @vue/cli-plugin-typescript
- 例如:"@vue/cli-service": "~4.5.0" 使用
yarn add -D @vue/cli-plugin-typescript@^4
安装
- 例如:"@vue/cli-service": "~4.5.0" 使用
- 使用 vue invoke typescript 运行插件
- 插件提供的配置项
- Use class-style component syntax?
- 是否使用类组件
- 类组件是通过 typescript 提供的装饰器实现了通过写一个类来写 vue 组件的方法,对 typescript 有更好的支持。
- 但是官方配套的库并不能完美解决 typescript 的支持,需要 vue-tsx-support 提供额外支持
- 新项目建议不选择,直接使用 composition API,虽然它也需要 vue-tsx-support 提供支持,但是这种代码组织方式更解耦
- Use Babel alongside TypeScript (required for modern mode, auto-detected polyfills, transpiling JSX)?
- 是否在 typescript 编译后使用 babel
- typescript 具备转换 ts 到指定某 es 版本的能力,但是不具备 babel 提供的其他转换代码的能力。
- 例如:typescript 虽然能够把 jsx 转换为 javascript,但是转换的结果不能满足 vue 的要求,依然需要 babel 进行二次转换
- 建议开启
- Convert all .js files to .ts?
- 是否转换所有的 js 为 ts 文件,建议选择否。对于旧项目而言,这种操作会导致大量的 ts 文件类型报错
- Allow .js files to be compiled?
- 是否允许编译 js,让 ts 编译 js 文件
- Skip type checking of all declaration files (recommended for apps)?
- 跳过所有类型文件的检查,因为类型文件通常是外部库提供的,检查这些类型文件将会降低编译速度
- Use class-style component syntax?
- 运行插件后续钩子
- typescript 插件会根据当前项目插件安装情况修改文件
- 当前项目同时安装了 eslint 插件的话,会修改 .eslintrc.js 文件
- 会修改 main.js 为 main.ts
- 会增加 shims-tsx.d.ts 和 shims-vue.d.ts 等文件,详情见下文的文件解析
- 如果项目中已经安装了 eslint 插件,由于增加了对 typescript 的格式检查支持,eslint 的钩子会被调用
- 建议在格式化之前暂存文件,然后恢复被格式化的文件
- typescript 插件会根据当前项目插件安装情况修改文件
其他配套操作
别名
- 使用 tsconfig-paths-webpack-plugin,把 tsconfig.json 中配置的别名同步到 webpack 中
// vue.config.js
{
chainWebpack: (config) => {
config.resolve
.plugin("tsconfig-paths")
.use(require("tsconfig-paths-webpack-plugin"));
},
}
jsx
- 3.0 比较不一样
module.exports = {
presets: [
'@vue/app'
]
}
- 4.0、5.0 由 @vue/cli-plugin-babel 提供支持
- 默认使用 Babel 7 + babel-loader + @vue/babel-preset-app
- @vue/babel-preset-app 由以下库对 Vue JSX 语法提供支持
- @babel/plugin-syntax-jsx 支持 babel 解析 jsx 语法
- @vue/babel-preset-jsx 提供了 jsx 支持的语法,但由于 Babel 7的 bug,并非所有功能都支持
- 配置
- compositionAPI 在 setup 中返回渲染函数需要单独打开,下面代码中提供了配置方法
- 异常
- vOn: 暂时未能使用
- vModel 当自定义组件具有 model 属性时会出现错误,例如:el-form
- 组成库
- @vue/babel-sugar-composition-api-inject-h、@vue/babel-sugar-composition-api-render-instance 支持 compositionApi 在 setup 方法中返回渲染函数
- compositionApi 函数式组件
const Test = (props, { refs, emit, ... }) => { return () => <h1>Hello World!</h1>; }
- compositionApi 函数式组件
- @vue/babel-sugar-functional-vue 支持函数式组件及以函数的方式写简易组件
- 函数式组件
export const A = ({ props, listeners, children, data, ... }) => <div onClick={listeners.click}>{props.msg}</div>
- 以函数的方式写简易组件,变量名不同
export const b = ({ props, listeners }) => <div onClick={listeners.click}>{props.msg}</div>
- 函数式组件
- @vue/babel-sugar-inject-h 给 jsx 函数自动注入 h 函数
- @vue/babel-sugar-v-model 支持 vModel 语法
- @vue/babel-sugar-v-on 支持 vOn 语法
- @vue/babel-plugin-transform-vue-jsx 支持其他的一些语法,包括domPropsInnerHTML等
- @vue/babel-helper-vue-jsx-merge-props
- @vue/babel-sugar-composition-api-inject-h、@vue/babel-sugar-composition-api-render-instance 支持 compositionApi 在 setup 方法中返回渲染函数
- 配置
module.exports = {
presets: [
[
"@vue/cli-plugin-babel/preset",
{
jsx: {
compositionAPI: true,
},
},
],
],
};
composition-api
- 安装并引用 composition-api,一种 vue 官方提供的,更好代码组织方式,支持 ts,支持 vue2。
- 部分文档
- getCurrentInstance 获取当前组件实例,用来代替 this
ts支持现状
- 内置元素
- .vue 文件
- .vue 文件中 vscode 1.66.2 和 vetur 0.35 支持内置元素属性的输入提醒,但是输入错误没有报错
- jsx 模式
- jsx 中需要使用 vue-tsx-support 外部库支持
- .vue 文件
- 自定义元素
- prop
- .vue
- .vue 文件 vetur 0.35 支持提醒,但不支持类型检查
- vetur 0.35 提供 vetur.experimental.templateInterpolationService 测试功能,用于为 template 中的绑定之获取 js 特性和类型。需要配合
<script lang='ts'>
- vetur 0.35 提供 vetur.validation.interpolation 测试功能,配合 vetur.experimental.templateInterpolationService 用于检查传入组件的 prop 类型是否正确
- 以上两种支持获取的类型并不准确,且实测中 vetur.experimental.templateInterpolationService 未能获得对应变量的类型
- jsx
- class
- vue-tsx-support 支持
- compositionAPI
- compositionAPI原生支持
- class
- .vue
- scope-slot
- .vue
- .vue 文件 vetur 0.35 不支持
- jsx
- class
- vue-tsx-support 支持
- compositionAPI
- vue-tsx-support 支持
- class
- .vue
- slot
- .vue
- .vue 文件 vetur 0.35 不支持
- jsx
- vue-tsx-support 不支持
- .vue
- emit
- .vue
- .vue 文件 vetur 0.35 不支持
- jsx
- class
- vue-tsx-support 支持
- compositionAPI
- compositionAPI 原生支持?
- class
- .vue
- prop
- 外部组件库
- .vue
- .vue 文件 vetur 0.35 提供了常用库的类型提醒,也支持通过配置 JSON 进行扩展
- jsx
- 外部组件库提供了 .d.ts 文件,但是不兼容 vue-tsx-support,需要手动转换
- .vue
- 推荐方案
- vetur 对类型的支持始终受限于插件的完成度,建议使用 jsx 代替 template,回归原生,对于类型支持更完善
其他疑难问题
- vue invoke eslint 和 vue invoke typescript 执行顺序的先后,会影响 .eslint.js 中的配置。
- 不清楚具体会有什么影响,建议建立一个对应版本新的项目,选择 eslint 和 typescript 插件,拷贝生成的 .eslint.js 文件
- 以下为 4.0 配置
module.exports = {
root: true,
env: {
node: true,
},
extends: [
"plugin:vue/essential",
"eslint:recommended",
"@vue/typescript/recommended",
"@vue/prettier",
"@vue/prettier/@typescript-eslint",
],
parserOptions: {
ecmaVersion: 2020,
},
rules: {
"no-console": process.env.NODE_ENV === "production" ? "warn" : "off",
"no-debugger": process.env.NODE_ENV === "production" ? "warn" : "off",
},
};
- .vue文件中的data或methods下的方法报 Missing return type on function 错误
- 这是由于 .vue 的 script 标签 export default 一个 {},该对象内的方法 eslint 检查时判断需要 return 的类型定义
- 只需要改为 export default Vue.extend({})
实操记录
环境
- "@vue/cli-service": "~4.5.0"
- "@vue/cli-plugin-typescript": "4"
修改的文件,文件解析来源于 TypeScript 3.1
-
.eslintrc.js
- parserOptions
- parser: "@typescript-eslint/parser"
- 支持解析 typescript 的解析器
- parser: "@typescript-eslint/parser"
- extends
- 新增 "@vue/typescript"
- parserOptions
-
tsconfig.json
- "compilerOptions"
- "target": "esnext",
- 转换的目标 es 版本
- "module": "esnext",
- 转换的目标模块关系: "None", "CommonJS", "AMD", "System", "UMD", "ES6"或 "ES2015"
- "strict": true,
- 启用所有严格类型检查选项,相当于启用
- noImplicitAny 禁止 any 类型
- noImplicitThis 禁止 this 为 any 类型
- alwaysStrict 以严格模式解析并为每个源文件生成 "use strict"语句
- strictNullChecks 禁止 null、undefined 赋值给其他类型(有个例外 void)
- strictFunctionTypes 进行严格的函数类型检查,禁止函数双向协变赋值
- 是否协变是指,复合类型Comp<T>和基础类型 T,兼容关系的说明
- 协变是指复合类型的兼容关系和基础类型的兼容关系一致
- 例如:T1={a:string} Comp1<T>={a:string}[]
- T2={a:string,b:number} Comp2<T>={a:string,b:unmber}[]
- T2 可以赋值给 T1,Comp2 也可以赋值给 Comp1,他们的兼容性一致,被成为协变
- 逆协变是指函数的参数存在逆协变
- 例如:T1={a:string} Comp1<T>=(arg:{a:string})=>void
- T2={a:string,b:number} Comp2<T>=(arg:{a:string,b:number})=>void
- T2 可以赋值给 T1,但是 Comp2<T> 不能够赋值给 Comp1<T>,刚好和上例中的协变相反
- Comp1<T> 在被调用时只要求 a 参数,而 Comp2<T> 在调用时需要 a、b 两个参数,把 Comp2<T> 赋值给 Comp1<T>,当 Comp1<T> 被调用时调用者根据 Comp1<T> 的类型传递参数 {a:'1'},这将不能满足函数 Comp2<T> 的需求
- 双向协变:上例中 Comp2<T> 被允许赋值 Comp1<T> 的话,即 strictFunctionTypes:false,这容易导致错误
- 不变表示 Comp<T> 类型与 T 类型双向都不兼容
- strictPropertyInitialization 确保类的非undefined属性已经在构造函数里初始化
- "jsx": "preserve"
- 在 .tsx文件里支持JSX
- 具有三种JSX模式:preserve,react和react-native
- 这些模式只在代码生成阶段起作用,类型检查并不受影响
- preserve模式下生成代码中会保留JSX以供后续的转换操作使用,输出文件会带有.jsx扩展名
- react模式会生成React.createElement,输出文件的扩展名为.js
- react-native相当于preserve,它也保留了所有的JSX,但是输出文件的扩展名是.js
- "importHelpers": true,
- 从 tslib 导入辅助工具函数,应该是用来提供常用函数,减少编译后文件体积大小的
- "moduleResolution": "node",
- 决定如何处理模块 module === "AMD" or "System" or "ES6" ? "Classic" : "Node"
- 决定的是模块的解析方式,根据那种规则寻找 import 路径对应的文件
- "allowJs": true,
- 允许编译 javascript 文件
- "skipLibCheck": true,
- 忽略所有的声明文件( *.d.ts)的类型检查
- 当程序包含大型声明文件时,编译器会花费大量时间对已知不包含错误的声明进行类型检查,而跳过声明文件类型检查可能会显着缩短编译时间。
- "esModuleInterop": true,
- 支持使用import d from 'cjs'的方式引入 commonjs 包
- "allowSyntheticDefaultImports": true,
- 允许从没有设置默认导出的模块中默认导入。这并不影响代码的输出,仅为了类型检查。
- "sourceMap": true,
- 生成相应的 .map文件
- "baseUrl": ".",
- 解析非相对模块名的基准目录
- "types": []
- 要包含的类型声明文件名列表。
- "webpack-env"
- "paths":
- 模块名到基于 baseUrl的路径映射的列表
- "@/*": []
- "src/*"
- "lib": []
- 编译过程中需要引入的库文件的列表,应该是类似于 polyfill 的存在
- "esnext",
- "dom",
- "dom.iterable",
- "scripthost"
- "target": "esnext",
- "include": []
- "include"和"exclude"属性指定一个文件glob匹配模式列表。"files"指定一个包含相对或绝对文件路径的列表,优先级高于前两者,编译时取他们的并集。
- "src/**/*.ts",
- "src/**/*.tsx",
- "src/**/*.vue",
- "tests/**/*.ts",
- "tests/**/*.tsx"
- "exclude": []
- "node_modules"
- "compilerOptions"
-
shims-tsx.d.ts
- 该文件用来定义和jsx相关的类型,但是官方给出的定义相当敷衍,建议使用vue-tsx-support库代替
- declare global
- namespace JSX
- JSX 中固有元素<div>总是以一个小写字母开头,基于值的元素<MyComponent>总是以一个大写字母开头。
- 有两种方式可以定义基于值的元素,无状态函数组件 (SFC)和类组件
- interface Element extends VNode {}
- 无状态组件的返回类型限定
- 无状态组件的第一个参数是 props 对象。 TypeScript会强制它的返回值可以赋值给 JSX.Element。即可以通过定义 JSX.Element 来自定义检查无状态函数组件的返回类型
- 这是 typescript 的要求,vue 是不支持这种写法的,需要 vuejs/jsx-vue2 进行转换
- 例如:
export default ({ props }) => <p>hello {props.message}</p>
- interface ElementClass extends Vue {}
- 类组件实例的类型限定
- 类组件的实例类型必须赋值给 JSX.ElementClass 或抛出一个错误,即可以通过 JSX.ElementClass 来自定义元素实例类型的检查方法
- interface IntrinsicElements
- 固有元素用来查找元素和属性。例如:{input:{readonly?:boolean}}
[elem: string]: any;
- 使固有元素可以是任意字符串,且可以具有任意属性
- 这是不严谨的,导致固有元素不能出现正确的属性提醒
- 其他与 jsx 相关
- interface ElementAttributesProperty{}
- 基于值的元素,用该属性来定义哪个属性作为元素属性使用
- 如果未指定JSX.ElementAttributesProperty,那么将使用类元素构造函数或SFC调用的第一个参数的类型
- 如果一个属性名不是个合法的JS标识符(像data-*属性),并且它没出现在元素属性类型里时不会当做一个错误。
- interface IntrinsicAttributes{}
- 该接口用来指定额外的属性,这些额外的属性通常不会被组件的props或arguments使用
- type IntrinsicClassAttributes
- 该接口也用来指定额外的属性,这里的泛型参数表示类实例类型
- type LibraryManagedAttributes<C, P>
- 提供了一个元素属性运算获取的方法,C 为基于值的元素自身类型,P接收的应该是函数式组件的泛型
- 这是 vue-tsx-support 的实现方式
- ElementAttributesProperty{}
- 从TypeScript 2.3开始引入了children类型检查,该选项用于决定children名
- 用于检查插槽是否插入正确的元素
- 编译选项使用的工厂函数是可以通过jsxFactory配置
- 即编译时以上这些配置项可以被整体替换,也就是本来去 global.JSX 寻找的方法 改为到 global.jsxFactoryName.JSX 下寻找
- jsxFactory 在 tsconfig.json 中配置,vue-tsx-support 目前采用该方式整体替换
- interface ElementAttributesProperty{}
- namespace JSX
-
shims-vue.d.ts
- 为 .vue 文件添加模块声明
- .vue 文件不太适合 ts 的全面支持,不推荐使用,但可以保留该文件的声明,不会有影响。
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
- main.js 会被修改文件名后缀为 main.ts,这是必须的,typescript 要求入口文件为 ts 类型
- HelloWorld.vue 这是一个范例文件,建议删除
- Home.vue 这是一个范例文件,建议删除
新增的依赖
- "@typescript-eslint/eslint-plugin": "^4.18.0"
- "@typescript-eslint/parser": "^4.18.0"
- "@vue/eslint-config-typescript": "^7.0.0"
- "typescript": "~4.1.5"