# TypeScript工程

# 1、模块系统

常用commonjs、es6模块、umd模块(and、commonjs、全局变量)

  • ES6模块
    • 导出:export、export default
    • 导入:import
  • commonjs
    • 导出:exports、module.exports
    • 导入:require
  • Typescript:es6中export default导出和commonjs的require导入不兼容,解决方法
    • 两种模块系统不要混用
    • 兼容性写法
      • es6模块中:export = x
      • commonjs模块中:import x = require
        • "esModuleInterop": true配置开启,可使用import x from './a'导入
//一、es6模块规范:c.ts -> a.ts -> b.ts
//a.ts导入模块
//单独导出
 export let a = 1

 //批量导出
 let b = 2
 let c = 3
 export {b, c}

 //导出接口
 export interface P{
    x: number;
    y: number;
 }

 //导出函数
 export function f(){}

 //导出时起别名
 function g() {}
 export {g as G}

 //默认导出,无需函数名
 export default function(){
     console.log("I'm default")
 }

 //引入外部模块,重新导出
 export { str as hello } from './b'

//b.ts导出常量
export const str = "Hello"

//c.ts模块导入
import {a, b} from './a' //批量导入
import {P} from './a'    //接口导入 
import {f as F} from './a'  //导入时起别名
import * as All from './a'  //导入模块中的所有成员,绑定在All上
import myFunction from './a' //不加{},导入默认
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
//一、commonjs模块规范:c.ts -> a.ts | b.ts
//a.ts
let a = {
    x: 1,
    y: 2
}
//整体导出
module.exports = a

//b.ts
//exports === module.exports
//导出多个变量
exports.c = 3
exports.d = 4

//c.ts
//导入
let c1 = require('./a')
let c2 = require('./b')

//node默认编译js文件,不支持ts文件
//执行需要使用ts-node
//npm i ts-node -g
//ts-node ./c.ts
console.log(c1, c2)
//{ x: 1, y: 2 } { c: 3, d: 4 }
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

# 2、命名空间

用于避免全局污染,目前已使用模块系统代替,全局类库中命名空间仍然是个好的解决方案,ts使用命名空间主要用于兼容全局类库

  • 实现原理:立即执行函数构成的闭包
  • 要点
    • 局部变量对外不可见
    • 导出成员对外可见
    • 多个文件可共享同名命名空间
    • 依赖关系:///<reference path="" />
  • 不要与模块混用
//namespace1.ts
namespace Shape {
    const pi = Math.PI
    export function circle(r: number) {
        return pi * r ** 2
    }
}

//namespace2.ts
//1. 命名空间可拆分为多个文件
//2. 命名空间不要在模块中使用,最好在全局中使用
///<reference path="./namespace2.ts" />
namespace Shape {
    export function square(x: number) {
        return x * x
    }
}
Shape.circle(1)
Shape.square(2)

//命名空间别名,使用import,和模块无关系
import circle = Shape.circle
circle(2)


//编译实现,本质使用闭包
//tsc namespace2.ts
//namespace2.js
var Shape;
(function (Shape) {
    function square(x) {
        return x * x;
    }
    Shape.square = square;
})(Shape || (Shape = {}));
Shape.circle(1);
Shape.square(2);
var circle = Shape.circle;
circle(2);
  
//namespace1.js
var Shape;
(function (Shape) {
    var pi = Math.PI;
    function circle(r) {
        return pi * Math.pow(r, 2);
    }
    Shape.circle = circle;
})(Shape || (Shape = {}));
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

# 3、声明合并

  • 含义:编译器将多个具有名称的声明合并为一个声明,可把程序中多个重名声明合并在一起,用于兼容老模块

  • 接口之间合并

    • 非函数成员保证一致性
    • 函数成员成为函数重载,重载顺序为:
      • 函数参数为字符串字面量,提升到最顶端
      • 接口之间,后面的接口靠前
      • 接口内部,按书写顺序排序
  • 命名空间之间合并:导出的成员不可重复定义或实现

  • 命名空间与类合并:为类添加静态成员,命名空间要在类和函数之后

  • 命名空间与函数合并:为函数添加属性和方法

  • 命名空间与枚举合并:为枚举添加属性和方法

// 接口声明合并
interface Aa {
    x: number;
    //非函数成员保证一致性
    //y: string;
    foo(bar: number): number;  //5
    foo(bar: 'a'): number;     //2
}
interface Aa {
    y: number;
    foo(bar: string): string;     //3
    foo(bar: number[]): number[]; //4
    foo(bar: 'b'): number;    //1
}
let aaa : Aa = {
    x: 1,
    y: 1,
    foo(bar: any){
        return bar
    }
}

//命名空间合并
//1. 命名空间与函数合并:为函数添加属性和方法
function Lib(){}
namespace Lib{
    export let version = '1.0'
}
console.log(Lib.version) //1.0

//2. 命名空间与类合并:为类添加静态成员,命名空间要在类和函数之后
class C {}
namespace C {
    export let state = 1
}
console.log(C.state)

//3. 命名空间与枚举合并:为枚举添加属性和方法,顺序无限制
enum Color {
    Red,
    Yellow,
    Bule
}
namespace Color {
    export function mix(){}
}
console.log(Color.mix())
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

# 4、声明文件

  1. 引用外部类库时,若类库使用非ts编写时,必须对其添加声明文。大部分类库,社区已配置声明文件,无声明文件的需自己编写。

    查询网址:是否配置声明文件查询 (opens new window)

  2. 类库分类:

  • 模块类库:通过模块方式引入
  • umd类库:既可以通过全局变量引用,又可以通过模块方式引用
  • 全局类库:通过全局变量引用
//一、jquery为umd类库
//1. 安装类库和类型声明包
//npm i jquery
//npm i @types/jquery -D

//2. 用模块方式使用类库
import $ from 'jquery'
$('.app').css('color', 'red')
1
2
3
4
5
6
7
8

# 1、全局库

//global-lib.js
function globalLib(options){
    console.log(options)
}
globalLib.version = '1.0.0'
globalLib.doSomething = function(){
    console.log('globalLib do something')
}

//global-lib.d.ts
declare function globalLib(options: globalLib.Options): void;
declare namespace globalLib {
    const version: string;
    function doSomething(): void;
    //可放全局,同下
    interface Options {
        [key: string]: any
    }
}

//使用index.ts
globalLib.doSomething()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# 2、模块类库

//module-lib.js
function moduleLib(options){
    console.log(options)
}
const version = '1.0.0'
function doSomething(){
    console.log('moduleLib do something')
}
moduleLib.version = version
moduleLib.doSomething = doSomething
module.exports = moduleLib

//module-lib.d.ts
declare function moduleLib(options: Options): void
interface Options {
    [key: string]: any
}
declare namespace moduleLib {
    //export可省略
    export const version: string
    function doSomething(): void
}
//兼容性写法
export = moduleLib
  
//使用
import moduleLib from './module-lib'
moduleLib.doSomething()
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

# 3、umd类库

//umd-lib.js
(function (root, factory){
    if(typeof define === 'function' && define.amd){
        define(factory)
    }else if(typeof module === 'object' && module.exports){
        module.exports = factory();
    }else{
        root.umdLib = factory();
    }
}(this, function(){
    return {
        version: '1.0.0',
        doSomething(){
            console.log('umdLib do something')
        }
    }
}));

//umd-lib.d.ts
declare namespace umdLib {
    const version: string
    function doSomething(): void
}
//umd专用语句
export as namespace umdLib
export = umdLib
  
//使用index.ts
//1. umd类库模块引入
import umdLib from './umd-lib'
umdLib.doSomething()

//2. umd类库全局引入
//tsconfig.json:"allowUmdGlobalAccess": true,   
//<script src="./umd-lib.js"></script>
//umdLib.doSomething()
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

# 4、其他

  • 类库自定义方法
  • 类库依赖
//类库定义方法
//1. 给模块定义自定义方法
//npm i moment
import m from 'moment'
declare module 'moment' {
    export function myFunction(): void
}
m.myFunction = () => {}

//2. 给全局定义方法
declare global {
    namespace globalLib {
        function doAnything(): void
    }
}
globalLib.doAnything = () => {}


//类库依赖
//jquery为例子:
//@type/jquery中package.json:type字段可查看声明文件入口index.d.ts
/*
//模块依赖:ts在@types中寻找模块,查找到sizzle目录,找到对应依赖文件
/// <reference types="sizzle" />
//路径依赖:ts在同级路径下查找声明文件
/// <reference path="JQueryStatic.d.ts" />
/// <reference path="JQuery.d.ts" />
/// <reference path="misc.d.ts" />
/// <reference path="legacy.d.ts" />
export = jQuery;
*/
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

# 5、tsconfig.json主要配置

# 1、文件选项

  • files:需要编译的单个文件列表
  • include:需要编译的文件或目录
  • exclude:需要排除的文件或目录
  • extends:配置文件继承
{
  "extends": "./tsconfig.base", //继承基础配置,已下会覆盖
  "files": ["src/a.ts"], //数组
  "include": ["src"], //编译src及子目录下文件
  //"include": ["src/*"], 支持通配符,编译src一级目录下文件
  //"include": ["src/*/*"], 编译src二级目录下文件
  "exclude": ["src/lib"], //默认排除node_modules文件夹下内容
  "compileOnSave": true //编译后自动保存,该选项vscode不支持
}
1
2
3
4
5
6
7
8
9

# 2、编译选项

  • incremental:增量编译,生成tsconfig.tsbuildinfo文件
  • target:目标语言
  • module:目标语言系统
  • outFile:将多个依赖文件生成一个文件(amd模块使用)
  • lib:引用类库
  • allowJs:运行编译JS文件
  • outDir:输出目录
  • rootDir:输入目录,用于调整输出目录结构
  • declaration:生成声明文件
  • sourceMap:生成sourceMap
  • noEmit:不输出文件,ts只做类型检查时可使用
  • strict:严格模式检查
  • esModuleInterop:允许export = 导出,由import from导入
  • allowUmdGlobalAccess:允许在模块中访问UMD全局变量
  • moduleResolution:模块解析策略
  • baseUrl:解析非相对模块的基地址
  • paths:路径映射,想对于baseUrl
  • rootDirs:将多个目录放在一个虚拟目录下,方便运行时访问
{
  "compilerOptions":{
    "incremental": true,  //增量编译
    "tsBuildInfoFile": "./buildFile", //增量编译文件的输出位置
    "diagnostics": true,  //输出诊断信息
    
    "target": "es5",      //目标语言的版本
    "module": "commonjs",	//生成代码的模块标准
    "outFile": "./app.js",//将多个相互依赖的文件生成一个文件,可以用在amd模块中
    "lib": ["es2019.array"],		//ts需要引入的库,即声明文件
    
    "allowJs": true, //运行编译Js文件,js/jsx
    "checkJs": true, //允许在JS文件中报错,通常与allowJs一起使用
    "outDir": "./out", //指定输出目录,输出文件放置在该目录
    "rootDir": "./",   //指定输入文件目录(用于输出目录结构控制)
    //当前目录结构:src/index.ts、tsconfig.json
    //配置rootDir为./,输出为:src/index.js
    //配置rootDir为./src,输出为:index.js
    
    "declaration": true, //生成声明文件
    "declarationDir": "./d", //生成文件的路径
    "emitDeclarationOnly": true, //只生成声明文件
    "sourceMap": true, //生成目标文件的sourceMap
    "inlineSourceMap": true, //生成目标文件的inline sourceMap
    "declarationMap": true, //生成 声明文件的sourceMap
    "typeRoots": [], //声明文件目录,默认node_modules/@types
    "types":[],      //声明文件包
    
    "removeComments": true, //删除注释
    "noEmit": true, //不输出文件
    "noEmitOnError": true, //发生错误时不输出文件
    
    "strict": true, //开启所有严格的类型检查,为true后下方全为true
    "alwaysStrict": false, //在代码中注入"use strict"
    "noImplicitAny": false,//不允许隐式的any类型
    //function(x){}
    "strictNullChecks": false, //不允许把null、undefined赋值给其他类型变量
    //let x: number = null
    "strictFunctionTypes": false,//不允许函数参数双向协变
    //let a = (x: number) => null
    //let b = (y?: number) => null
    //b = a
    "strictPropertyInitialization": false, //类的实例属性必须初始化
    //class C{ x: number}
    "strictBindCallApply": false, //严格的bind/call/apply检查
    //function add(x: number, y: number) {return x + y}
    //add.call(undefined, 1, '2')
    "noImplicitThis": false, //不允许this有隐式的any类型
    /* class A { 
    			a: number = 1; 
    			getA(){
    				return function(){
    					console.log(this.a)
    				}
    			}		
    	 }
    	 let a = new A().getA()
    	 a()//undefined,this指向问题
    */
    
    "noUnusedLocals": false, //检查只声明,未使用的局部变量
    "noUnusedParameters": true, //检查未使用的函数参数
    "noFallthoughCasesInSwitch": true, //防止switch语句贯穿
    "noImplicitReturns": true,         //每个分支都要有返回值
    
    "esModuleInterop": true,   //允许export = 导出的模块,由import from导入
    "allowUmdGlobalAccess": true, //允许在模块中访问UMD全局变量
    "moduleResolution": "node", //模块解析策略
    
    /* 模块解析策略解析:classic、node,默认为node解析策略
    1. classic解析策略:amd、system、es2015
    /root/src/moduleA.ts
    想对导入:import {b} from "./moduleB"
    /root/src/moduleB.ts
    /root/src/moduleB.d.ts
    非相对导入:import {b} from "moduleB"
    /root/src/node_modules/moduleB.ts
		/root/src/node_modules/moduleB.d.ts
		/root/node_modules/moduleB.ts
		/root/node_modules/moduleB.d.ts
		/node_modules/moduleB.ts
		/node_modules/moduleB.d.ts
		
		2. node解析策略
		/root/src/moduleA.ts
    相对导入:import {b} from "./moduleB"
    /root/src/moduleB.ts
		/root/src/moduleB.tsx
		/root/src/moduleB.d.ts
		/root/src/moduleB/package.json ("types"属性)
		/root/src/moduleB/index.ts
		/root/src/moduleB/index.tsx
		/root/src/moduleB/index.d.ts
    非相对导入:import {b} from "moduleB"
    /root/src/node_modules/moduleB.ts
		/root/src/node_modules/moduleB.tsx
		/root/src/node_modules/moduleB.d.ts
		/root/src/node_modules/moduleB/package.json ("types"属性)
		/root/src/node_modules/moduleB/index.ts
		/root/src/node_modules/moduleB/index.tsx
		/root/src/node_modules/moduleB/index.d.ts
		//向上一级目录查找:moduleB -> package.json -> index(ts\tsx\d.ts)
		/root/node_modules/moduleB.ts
		/root/node_modules/moduleB.tsx
		/root/node_modules/moduleB.d.ts
		/root/node_modules/moduleB/package.json ("types"属性)
		/root/node_modules/moduleB/index.ts
		/root/node_modules/moduleB/index.tsx
		/root/node_modules/moduleB/index.d.ts
		//查找node_modules
		/node_modules/moduleB.ts
		/node_modules/moduleB.tsx
		/node_modules/moduleB.d.ts
		/node_modules/moduleB/package.json ("types"属性)
		/node_modules/moduleB/index.ts
		/node_modules/moduleB/index.tsx
		/node_modules/moduleB/index.d.ts
    */
    
    "baseUrl": "./", //解析非相对模块的基地址
    "paths": {       //路径映射,相对于baseUrl
      "jquery": ["node_modules/jquery/dist/jquery.slim.min.js"]
    },
    //src和out编译器认为在一个目录下
    "rootDirs": ["src", "out"], //将多个目录放在一个虚拟目录下,用于运行时
    
    "listEmittedFiles": true, //打印输出的文件
    "listFiles": true,				//打印编译的文件(包括引用的声明文件)
  }
}
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
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

# 3、工程引用

ts3.0引入的新的特性(工程引用),应用于工程中放置多个工程目录情况

总体思路:将每个工程添加tsconfig.json配置,总体再配置tsconfig.json,再单独构建工程

  • 解决了输出目录引用问题
  • 解决了单个工程构建问题
  • 通过增量编译提升了构建速度

配置选项:

  • composite:工程可以被引用或进行增量编译
  • declaration:必须开启
  • reference:该工程所依赖的工程
  • tsc --build 模式:单独构建一个工程,依赖工程也会被构建
//单独构建工程,对于的依赖工程也会构建
tsc -b src/server --verbose
tsc -b src/client --verbose
tsc -b test --clean

//目录结构
/*
-src
	-client
		-tsconfig.json
	-common
		-tsconfig.json
	-server
		-tsconfig.json
-test
	-tsconfig.json
-tsconfig.json
*/
//1. client中tsconfig.json
{
  "extends": "../../tsconfig.json",
   "compilerOptions":{
     "outDir": "../../dist/client"
   },
   "references": [
     {"path": "../common"}
   ]
}
//2. common中tsconfig.json
{
  "extends": "../../tsconfig.json",
   "compilerOptions":{
     "outDir": "../../dist/common"
   }
}
//3. server中tsconfig.json
{
  "extends": "../../tsconfig.json",
   "compilerOptions":{
     "outDir": "../../dist/server"
   },
   "references": [
     {"path": "../common"}
   ]
}
//4. test中tsconfig.json
{
  "extends": "../tsconfig.json",
   "references": [
     {"path": "../src/client"},
     {"path": "../src/server"}
   ]
}
//5. 项目总体目录
{
  "compilerOptions":{
    "target": "es5",
    "module": "commonjs",
    "strict": true,
    "composite": true,
    "declaration": true
  }
}
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

# 5、编译工具

# 1、官方编译工具

ts编译工具使用ts推荐的ts-loader或awesome-typescript-loader插件,或使用babel配合ts类型检查实现

  • ts-loader:ts官方编译工具

    • 推荐默认配置
    • 配置项transpileOnly:只做语言转换,不做类型检查,提升性能,需配合插件进行类型检查
    • 需添加插件:开启独立的类型检查进程,fork-ts-checker-webpack-plugin
  • awesome-typescript-loader

    • 推荐默认配置
    • transpileOnly:只做语言转换,不做类型检查
    • CheckerPlugin(内置):独立的类型检查进程

解析:awesome-typescript-loader与ts-loader的区别

  1. 更适合于Babel集成,使用Babel的转义和缓存
  2. 不需要安装额外的插件,就可以把类型检查放在独立进程中进行

综合分析,awesome-typescript-loader比ts-loader有更长的编译时间,且transpileOnly+类型检查进程有部分遗漏,优先使用ts-loader

//一、ts-loader
//1. 安装:npm i ts-loader fork-ts-checker-webpack-plugin -D
//2. webpack配置
const ForTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin")
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/i,
        use: [{
          loader: 'ts-loader',
          options: {
            //提升性能:只做语言转换,不做类型检查,需配合插件进行类型检查
            transpileOnly: true
          }
        }],
      }
    ]
  },
  plugins: [
    new ForTsCheckerWebpackPlugin()
  ]
}

//二、awesome-typescript-loader
//1. 安装:npm i awesome-typescript-loader -D
//2. webpack配置
const {CheckerPlugin} = require('awesome-typescript-loader')
const ForTsCheckerWebpackPlugin = require("fork-ts-checker-webpack-plugin")
module.exports = {
  module: {
    rules: [
      {
        test: /\.tsx?$/i,
        use: [{
          loader: 'awesome-typescript-loader',
          options: {
            transpileOnly: true
          }
        }],
      }
    ]
  },
  plugins: [
    new CheckerPlugin()
  ]
}
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

# 2、babel

babel和tsc都有编译能力,将ts编译成js,tsc有类型检查而babel没有,但babel插件较丰富

babel7之前:TS -> tsp(ts-loader/awesome-typescript-loader) -> js -> babel -> js

babel7之后:TS -> Babel -> Js,通过tsc进行type checking

解析:如何选择typescript编译工具

  1. 如果没有使用babel,首选typescript自身的编译工具(可配合ts-loader使用)
  2. 如果项目中已经使用了Babel,安装@babel/preset-typescript(可配合tsc做类型检查)
  3. 两种编译工具不要混用

babel工程使用:

  • 只做语言转换
    • @babel/preset-typescript
    • @babel/proposal-class-properties:类支持
    • @babel/proposal-object-rest-spread:剩余操作符
  • tsc --watch 模式:做类型检查
  • 无法编译的typeScript特性
    • namespace:不要使用,不支持
    • <typename>类型断言:改用as typename
    • const enum:常量枚举无法使用,未来可期
    • export =:不推荐使用默认导出

# 6、代码检查工具

两种代码检查工具:tslint、eslint

  • tslint:官方弃用
  • eslint:官方体系
    • eslint生成的ast与TypeScript的ast不兼容
    • 可使用typescript-eslint插件将ts的ast转换为eslint的estree
      • @typescript-eslint/parser:替换Eslint的解析器
      • @typescript-eslint/eslint- plugin:使Eslint能够识别一些特殊的ts语法
    • vscode eslint插件:eslint.autoFixOnSave
  • Babel-eslint:适用Babel体系
  1. 解析:使用了typescript,为什么还使用eslint

ts可以做类型检查,语言转换可识别部分语法错误,eslint除识别语法错误外可保证代码风格统一

  1. Babel-eslint与typescript-eslint的区别
    • babel-eslint:支持TypeScript没有的额外的语法检查,抛弃了typescript,不支持类型检查
    • typescript-eslint:基于typescript的ast,支持创建基于类型信息的规则(tsconfig.json)
    • 建议:两者底层机制不一致,不要一起使用。Babel体系建议使用babel-eslint,否则可以使用typescript-eslint

# 7、单元测试

  • ts-jest:有类型检查
  • babel-jest:无类型检查,需配合tsc --watch模式使用
//1. 安装
//ts-jest
npm i jest ts-jest @types/jest -D
npx ts-jest config:init
//babel-jest
npm i jest bable-jest @types/jest @types/node -D

//2. 工具库
function add(a: number, b: number){
    return a + b
}
function sub(a: number, b: number){
    return a - b
}
module.exports = {
    add,
    sub
}

//3. 测试脚本
const math = require('../src/math')
test('add: 1 + 1 = 2', ()=>{
    expect(math.add(1, 1)).toBe(2);
})
test('add: 1 - 2 = -1', ()=>{
    expect(math.sub(1, 2)).toBe(-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

# 8、工具体系

  • babel系:编译工具@babel/preset-typescript、代码检查工具babel-eslint、单元测试工具babel-jest
  • 非babel系:编译工具ts-loader、代码检查工具babel-eslint、单元测试工具babel-jest

# 9、迁移策略

引入ts是一个渐进式的过程:由共存 -> 宽松 -> 严格模式

# 1、共存模式

原js代码不动,新增代码用ts编写,步骤如下:

  • 添加ts(x)文件
  • 安装typescript
  • 选择构建工具
    • 保留Babel
      • 安装@babel/preset-typescript
      • 修改webpack配置
      • 添加类型检测:tsc --wacth模式
    • 放弃Babel
      • 安装ts-loader
      • 修改webpack配置
  • 检查js
    • allowJs: true
    • checkJs: true
  • 处理js报错
    • //@ts-nocheck
    • JSDoc注释
//一. 安装ts
npm i typescript @types/react @types/react-dom -D

//二. babel构建工具
npm i @babel/preset-typescript -D
//.babelrc配置文件,添加@babel/preset-typescript
{
  "presets":[
    "@babel/preset-typescript"
  ]
}
//webpack.base.config.js配置文件修改
module.exports = {
  resolve: {//添加ts和tsx
    extensions: ['.js', '.jsx', '.ts', '.tsx']
  },
  module: {
    rules: [
      {
        test: /\.(j|t)sx?$/, //添加tsx配置
        use:[{
          loader: 'babel-loader'
        }],
        exclude: /node_modules/
      }
    ]
  }
}
//三、添加类型检查
//1. tsconfig.json:不输出文件,对js进行编译
"include": [
  "./src/**/*"
],
"compilerOptions": {
    "allowJs": true, //检查js
  	"checkJs": true,
  	"noEmit": true
}

//2. package.json添加监控脚本
"type-check": "tsc --watch"

//四、处理js报错
//@ts-nocheck
//JSDoc注释, /** */
/**
 * 
 * @param {number} name 
 */
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

# 2、宽松策略

含义:将所有的js(x)文件重命名为ts(x)文件,在不修改代码的基础上,使用最宽松的类型检查规则

  • 使用shelljs重命名文件
  • 修改webpack入口配置
  • 修改tsconfig.json:strict设置为false
npm i shelljs @types/shelljs ts-node -D
//根文件添加renameJS.ts
import * as shelljs from "shelljs";
shelljs.find('src')
.filter(file => file.match(/\.jsx?$/))
.forEach(file => {
  let newName = file.replace(/\.j(sx?)$/, '.t$1');
  shelljs.mv(file, newName)
})
//package.json添加脚本
"rename-js": "ts-node renameJS.ts"
//webpack入口修改为.tsx
//tsconfig.json修改类型检查
 "strict": false,                                     
 "noImplicitAny": false,
 "noUnusedLocals": false,                          
 "noUnusedParameters": false,                       
 "noImplicitReturns": false,                      
 "noFallthroughCasesInSwitch": false, 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

# 3、严格模式

含义:开启最严格的类型检查规则,处理剩余的报错

步骤:strict设置为true后处理报错

# 10、现状和未来、ROI

  • 现状

    • 受关注程度:google Trends,热度搜索上升
    • 实际使用情况
      • npm trends,下载量攀升
      • github,贡献者使用语言,排名攀升
    • 使用后评价:stackoverflow,最受欢迎语言
  • 未来:前端发展趋势,js向语言右上角挣扎,学习类型思维补全前端短板

  • 投入产出比ROI:J型曲线,阵痛-受益-稳定

    • 收益:提升代码质量、增强代码可维护性、提升开发效率、重塑类型思维
    • 成本:思维转换、对接现有开发生态、项目迁移、接口和声明文件的维护
Last Updated: 1/30/2022, 9:34:23 PM