JavaScript到TypeScript的无痛迁移(React为例)

“无痛”的定义

  • 熟练后15分钟能迁移一个中小型项目
  • 仅接受少量甚至无需修改项目中的源码部分
  • 迁移后可以任然使用原来的语法编写项目
  • 迁移后不会给项目带来新的bug或坑

TypeScript 的定义

官网对 TypeScript(以下简称 TS)的定义如下:

  • TypeScript 是 JavaScript(以下简称JS) 的超集

怎么理解这句话呢?
先给出一张集合图:

TS集合

下面对这个集合的每个集合做简单介绍

  • TS语法
    1. TS语法包含了两大类语法, 分别是:
      1. JS的所有语法(ES5, ES6, ESNEXT)
      2. TS强类型语法(暂且这样不准确的称呼它, 它不仅仅只是强类型语法)
    2. 而TS语法不强制使用TS强类型语法, 即使完全使用JS语法来写程序都不会有问题.
    3. 描述申明实则是TS语法的一部分, 但在存在的形式上又可以脱离ts,tsx文件.
      这也是关键的TS是JS的超集的重要含义
    4. 这意味着只要把js或者jsx的后缀修改成对应的ts或tsx,
      并且在webpack里配置好ts相关的loader即可开始正常的开发. 实现了真正的无痛迁移.
  • 描述声明
    1. 函数传参强类型声明
    2. 函数返回强类型申明
    3. 函数使用方法申明
    4. 变量强类型申明
    5. interface申明, 可用于对象, 数组, JSON
    6. 继承申明
    7. 泛型申明
  • 增强提示
    1. 当有了TS的强类型语法后, 就意味着代码编辑器可以通过插件或自身的机制获得任何变量和对象的方法, 属性, 类型.
  • 语法检测
    1. 静态检测
    2. 伪动态检测
      这是因为当有了TS的强类型语法后, 就可以根据变量和对象申明的类型, 方法, 属性, 类型来判断它们是否被正确的使用.
      1. 穷举出可以传入的值, 并判断代码逻辑是否对这个值进行了过滤, 从来减少了不必要的值出入后出现异常报错
      2. 检测对象是否存在被使用的方法和属性, 当可能会在某种条件下不存在, 就会提示出来, 这也避免了后期不必要的对象或方法找不到的报错.
      3. 伪动态检测被调用的函数是否有返回值, 返回值的类型是否正确, 是否可能出现导致异常的情况.
        这点像是一边写代码一边简单的做了一遍mock测试的感觉.

总结一下, 就是在TS环境是可以完全使用JS语法写代码. 这让程序员们有个很好的过渡期.

迁移实践

先决条件

  • 迁移之前项目是可以正常运行, 正常开发, 正常工作的

项目环境

  • OS: Windows 10 Pro
  • node: 8.9.1
  • git-bash(即git版本): 2.16.3.windows.1
  • yarn: 1.5.1
  • typescript: 2.7.2
  • webpack: 2.7.0
  • react: 16.2.0
  • vscode: 1.21.1

项目目录结构

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
.
|-- deploy
|-- language
| |-- en_US.json
| |-- home
| |-- index.js
| |-- module
| |-- user
| `-- zh_CN.json
|-- package.json
|-- src // 源码目录
| |-- Controls.json
| |-- app.tsx
| |-- controllers
| |-- fonts
| |-- iconfont
| |-- images
| |-- index.js
| |-- index.tsx
| |-- layout
| |-- proxy.json
| |-- services
| |-- style
| |-- theme.json
| |-- tmp.html
| |-- types
| `-- utils
|-- tsconfig.json // TS配置文件
|-- tslint.json // TS语法限制文件
|-- ui
| `-- index.html // 前端启动html文件
|-- vendor // 通过webpack打包好的第三方库文件
| |-- fonts
| |-- js
| |-- manifest.json
| `-- style
|-- webpack.deploy.config.js // 用于生成可直接部署的前端文件
|-- webpack.dev.config.js // 开发时用于实施修改代码并调试
|-- webpack.dll.config.js // 提前打包第三方库代码
`-- ...

迁移操作

先在已有的项目目录下安装必要的组件,
这个项目之前已经可以使用webpack做dev模式开发和生成代码方式开发了,
这里以使用git-bash运行yarn来安装, 并且以下所有命令都是在git-bash中运行

1
yarn add typescript awesome-typescript-loader @types/node @types/react @types/react-dom -D

如果使用的第三方库有提供types库, 可以对应的添加, 比如我使用了lodash:

1
yarn add @types/lodash -D

types库里的文件为前面说的描述声明, 里面不会有代码的显示逻辑, 仅用于给代码编辑器提供增强提示和语法检测的依据
安装完成后添加文件./src/types/custom.d.ts, 内容如下

1
2
3
4
declare module '*.json' {
const value: any;
export default value;
}

此文件的意义在于使用以下方式导入json文件的时候不会报错

1
import * as example from './example.json'

修改webpack配置文件

以webpack.dev.config.js文件为例, webpack.deploy.config.js文件修改方法相同

  1. 修改文件入口
1
2
3
4
entry: {
// 原先是index.js, 改为index.tsx
app: ['index.tsx']
},
  1. 在module.rules数组中把原来全部关于babel-loader替换为awesome-typescript-loader
    并修改test为/.tsx?$/
1
2
3
4
5
6
7
8
9
10
11
12
{
enforce: 'pre',
test: /\.tsx?$/,
loader: 'awesome-typescript-loader',
include: [path.resolve(__dirname, 'node_modules', '*')]
},
{
enforce: 'pre',
test: /\.tsx?$/,
loader: 'awesome-typescript-loader',
include: [path.resolve(__dirname, 'src')]
},
  1. 在webpack.dev.config.js头部加入引用awesome-typescript-loader的引用
1
const { CheckerPlugin } = require('awesome-typescript-loader')
  1. 在plugins数组中添加以下信息
1
new CheckerPlugin(),
  1. 添加extensions, 即添加’.ts’, ‘.tsx’, ‘.jsx’三个后缀
1
extensions: ['.ts', '.tsx', '.jsx', '.js', '.json', '.less', '.css'],

以上就完成了对webpack.dev.config.js文件的全部修改.

  1. 批量将js, jsx文件修改成ts, tsx文件,
    由于我们这个项目之前没区分jsx后缀,
    全都是用的js后缀, 所以在项目根目录下使用以下命令修改:
1
find ./src -name "*.js" | awk -F "." '{print $2}' | xargs -i -t mv ./{}.js  ./{}.tsx

添加TS配置文件

  1. 在项目根目录添加tsconfig.json文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
{
"compilerOptions": {
"outDir": "deploy",
"sourceMap": true,
"module": "esnext",
"target": "esnext",
"moduleResolution": "node",
"types": ["node", "react", "react-dom"],
"noImplicitAny": false,
"baseUrl": ".",
"jsx": "react",
"allowJs": true,
"paths": {
"*": ["node_modules/*", "src/types/*"]
}
},
"awesomeTypescriptLoaderOptions": {
"transpileOnly": true
},
"compileOnSave": true,
"include": ["src/**/*"]
}

重点配置说明:

  • awesomeTypescriptLoaderOptions.transpileOnly: true代表当写法和TS语法不符合是, 不进行报错提示.
    这里重点重申一下, 即使设置为false打开报错, 前端依然可以正常运行, 原因前面讲了–TS是JS的超级.
    而暂时关闭报错是为了开发人员在没有熟悉TS写法的时候提供一个过渡期.

以上配置参数的含义请参考以下网站
tsconfig-json
awesome-typescript-loader

添加tslint配置文件

此文件结合vscode的tslint插件使用

  1. 在项目根目录添加tslint.json文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
{
"extends": ["tslint:recommended"],
"rulesDirectory": ["./src"],
"rules": {
"ordered-imports": false,
"no-unused-expression": false,
"member-ordering": [
false,
{
"order": [
"private-static-field",
"private-instance-field",
"public-static-field",
"public-instance-field",
"private-constructor",
"public-constructor",
"private-instance-method",
"protected-instance-method",
"public-instance-method"
]
}
],
"object-literal-sort-keys": false,
"arrow-parens": false,
"variable-name": [true, "ban-keywords", "check-format", "allow-leading-underscore", "allow-pascal-case"],
"class-name": true,
"no-shadowed-variable": false,
"comment-format": [true, "check-space"],
"indent": [true, "spaces"],
"one-line": [true, "check-open-brace", "check-whitespace"],
"no-var-keyword": true,
"quotemark": [true, "single", "avoid-escape", "jsx-double"],
"semicolon": [true, "always", "ignore-bound-class-methods"],
"encoding": true,
"whitespace": [
true,
"check-branch",
"check-decl",
"check-operator",
"check-module",
"check-separator",
"check-type"
],
"typedef-whitespace": [
true,
{
"call-signature": "nospace",
"index-signature": "nospace",
"parameter": "nospace",
"property-declaration": "nospace",
"variable-declaration": "nospace"
},
{
"call-signature": "onespace",
"index-signature": "onespace",
"parameter": "onespace",
"property-declaration": "onespace",
"variable-declaration": "onespace"
}
],
"no-internal-module": true,
"no-trailing-whitespace": true,
"no-null-keyword": false,
"prefer-const": true,
"jsdoc-format": true,
"trailing-comma": [
true,
{
"multiline": {
"objects": "never",
"arrays": "never",
"functions": "never",
"typeLiterals": "ignore"
},
"esSpecCompliant": true
}
]
}
}

重点配置说明:

  • “quotemark”: [true, “single”, “avoid-escape”, “jsx-double”], 意义是在TSX中使用单引号, 在JSX中使用双引号
    具体区别表现如下:
1
<Route path="MessageCenter" breadcrumbName={i18n.messageCenter}>
1
const i18n = Language.getLanguage('index');

以上配置信息请参考以下网站
TSLint core rules

  1. 在项目根目录添加.editorconfig文件
    PS: 这个文件配合vscode的”EditorConfig for VS Code”插件使用, 如果没需要可以不添加
1
2
3
4
5
6
7
8
9
root = true

[[*.{ts,tsx,js}]]
indent_style = space
indent_size = 2
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true

TS无法容忍的JS写法

因为客观原因, TS也会有不可容忍的JS写法, 这里总结了两种JS写法

  1. 导入JSON后在对象中使用…JsonObject的方式赋值
1
2
3
4
5
6
7
8
9
10
11
12
// 国际化组件
import en_US from '../../../language/user/en_US.json';
import zh_CN from '../../../language/user/zh_CN.json';

export const languagePack = {
en: {
...en_US,
},
zh: {
...zh_CN,
}
};

应该改成以下写法

1
2
3
4
5
6
7
8
// 国际化组件
import en_US from '../../../language/user/en_US.json';
import zh_CN from '../../../language/user/zh_CN.json';

export const languagePack = {
en: en_US,
zh: zh_CN
};
  1. 在之前的代码中导入js的时候带了.js后缀
1
import RegModal from './RegModal.js';

因为刚才已经把这些文件的后缀批量替换成了tsx, 所以应该改成以下写法

1
import RegModal from './RegModal';

运行项目

和之前方式一样去启动项目即可

vscode与TS相关的插件

  • Typescript Extension Pack
  • JSON to TS
  • Move TS
  • Paste JSON as Code
  • Prettier
  • Sort Typescript imports
  • tslint
  • TypeScript Hero
  • Typescript Importer
  • Typescript React code snippets
  • Typescript React/Redux Snippets
  • TypeScript Toolbox

临时关闭TS在vscode中的报错

在vscode中按以下方式打开”用户设置区”
文件->首选项->设置->用户设置区
在json实体中添加以下参数

1
"typescript.validate.enable": false

webpack配置文件完整版

React使用TypeScript的WebPack完整配置文件

参考

Webpack官方文档
Webpack中文文档
tsconfig-json
awesome-typescript-loader
TSLint core rules

显示 Gitment 评论