TypeScript 那些事儿

TypeScript 那些事儿

01 变量类型 😊

1
2
let 变量名 : 变量类型 = value
function ( 变量名:变量类型 ): 变量类型{}

数据类型

类型 例子 描述
number 1,22,2.5 任意数字
string ‘DuoR’,”bbb” 任意字符串
boolean true,false
字面量 其本身 限制变量的值就是该字面量的值
any * 任意类型
unknown * 类型安全的 any
void 空值 undefined 没有值
never 没有值 不能是任何值
object 对象值 js Object
array [1,2,3,] js array
tuple [4,5] 元组,表示一个已知元素数量和类型的数组
enum enum{A,B} 枚举
联合声明 boolean | string 可以是 boolean 和 string 类型
  • 变量类型之间是不可以互相赋值的,否则会报错

  • 声明和赋值是同时进行的,TS 可以自动对类型进行检测,可以省略定义类型的步骤

  • 类型断言,变量1 = <变量类型> 变量名2 或 变量1 = 变量名2 as 变量类型

  • 类型的别名, type 自定义类型名 = 变量类型(|,&)

  • typescrpt 3.4 引入 readonly, 修饰后,变量只读:

1
const arr: readonly string[]= ['1','2','3']

注意点:

a. 在 tsconfig.json 指定了"strictNullChecks":falseundefinednull 是可以赋值到其他类型的(string/num…),否则,nullundefined 只能赋值给 void 和它们各自的类型。

b. numberbigint 都是表示数字,但是这两个类相互不兼容的

函数

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
// 函数声明
function sum(num1: number, num2: number): number {
return num1 + num2
}

// 函数表达式
sum(98, 29)

let sub = (num1: number, num2: number): number => {
return num1 - num2
}
sub(98, 29)

// 函数重载
type Types = number | string
function add(a: string, b: string): string
function add(a: number, b: number): number
function add(a: number, b: string): string
function add(a: string, b: number): string
function add(a: Types, b: Types) {
if (typeof a === 'string' || typeof b === 'string') {
return a.toString() + b.toString()
}
return a + b
}
add('1', '2').split('')

特别的类型

never

表示的是那些永不存在的值的类型

两种情况:

1
2
3
4
5
6
7
8
9
10
11
// 异常
function err(msg: string): never {
// OK
throw new Error(msg)
}

// 死循环
function loopForever(): never {
// OK
while (true) {}
}

特别用法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 利用 never 类型的特性来实现全面性检查
type Foo = string | number
function controlFlowAnalysisWithNever(foo: Foo) {
if (typeof foo === 'string') {
// 这里 foo 被收窄为 string 类型
} else if (typeof foo === 'number') {
// 这里 foo 被收窄为 number 类型
} else {
// foo 在这里是 never
const check: never = foo
}
}
// 假如哪一天Foo新增boolean,ts就会抛出一个产生一个编译错误。确保 controlFlowAnalysisWithNever 方法总是穷尽了 Foo 的所有可能类型。
type Foo = string | number | boolean
// 使用 never 避免出现新增了联合类型没有对应的实现,目的就是写出类型绝对安全的代码。
unknown

unknownany的最大区别是: 任何类型的值可以赋值给any,同时any类型的值也可以赋值给任何类型。unknown 任何类型的值都可以赋值给它,但它只能赋值给unknownany

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 这种机制起到了很强的预防性,更安全,这就要求我们必须缩小类型,我们可以使用typeof、类型断言等方式来缩小未知范围:
function getDogName() {
let x: unknown
return x
}
const dogName = getDogName()
// 直接使用
const upName = dogName.toLowerCase() // Error
// typeof
if (typeof dogName === 'string') {
const upName = dogName.toLowerCase() // OK
}
// 类型断言
const upName = (dogName as string).toLowerCase() // OK
Number、String、Boolean、Symbol

首先,我们来回顾一下初学 TypeScript 时,很容易和原始类型 number、string、boolean、symbol 混淆的首字母大写的 Number、String、Boolean、Symbol 类型,后者是相应原始类型的包装对象,姑且把它们称之为对象类型。

从类型兼容性上看,原始类型兼容对应的对象类型,反过来对象类型不兼容对应的原始类型。

下面我们看一个具体的示例:

1
2
3
4
let num: number
let Num: Number
Num = num // ok
num = Num // ts(2322)报错

此,我们需要铭记不要使用对象类型来注解值的类型,因为这没有任何意义。

object、Object 和 {}

另外,object(首字母小写,以下称“小 object”)、Object(首字母大写,以下称“大 Object”)和 {}(以下称“空对象”)

小 object 代表的是所有非原始类型,也就是说我们不能把 number、string、boolean、symbol 等 原始类型赋值给 object。在严格模式下,nullundefined 类型也不能赋给 object。

1
2
3
4
5
6
7
8
// object
let lowerCaseObject: object
lowerCaseObject = 1 // ts(2322)
lowerCaseObject = 'a' // ts(2322)
lowerCaseObject = true // ts(2322)
lowerCaseObject = null // ts(2322)
lowerCaseObject = undefined // ts(2322)
lowerCaseObject = {} // ok

大 Object 代表所有拥有 toString、hasOwnProperty 方法的类型,所以所有原始类型、非原始类型都可以赋给 Object。同样,在严格模式下,null 和 undefined 类型也不能赋给 Object。

1
2
3
4
5
6
7
8
// Object
let upperCaseObject: Object
upperCaseObject = 1 // ok
upperCaseObject = 'a' // ok
upperCaseObject = true // ok
upperCaseObject = null // ts(2322)
upperCaseObject = undefined // ts(2322)
upperCaseObject = {} // ok

综上结论:{}、大 Object 是比小 object 更宽泛的类型(least specific),{} 和大 Object 可以互相代替,用来表示原始类型(null、undefined 除外)和非原始类型;而小 object 则表示非原始类型。

字面量

在 TypeScript 中,字面量不仅可以表示值,还可以表示类型,即所谓的字面量类型。

1
2
3
4
// 目前,TypeScript 支持 3 种字面量类型:字符串字面量类型、数字字面量类型、布尔字面量类型
let specifiedStr: 'this is string' = 'this is string'
let specifiedNum: 1 = 1
let specifiedBoolean: true = true

应用场景:

比如声明如下所示的一个类型 Config:

1
2
3
4
5
interface Config {
size: 'small' | 'big';
isEnable: true | false;
margin: 0 | 2 | 4;
}

需要注意 :

在缺省类型注解的情况下,TypeScript 推断出它的类型直接由赋值字面量的类型决定

1
2
3
const str = 'this is string' // str: 'this is string'
const num = 1 // num: 1
const bool = true // bool: true

02 ts 专业名词 🧐

类型推断

在很多情况下,TypeScript 会根据上下文环境自动推断出变量的类型,无须我们再写明类型注解。

1
2
3
4
5
6
7
let str: string = 'I am a string'
let num: number = 1
let bool: boolean = true
// 简化后 ----->
let str = 'I am a string'
let num = 1
let bool = true

我们把 TypeScript 这种基于赋值表达式推断类型的能力称之为类型推断

在 TypeScript 中,具有初始化值的变量、有默认值的函数参数、函数返回的类型都可以根据上下文推断出来。比如我们能根据 return 语句推断函数返回的类型,如下代码所示:

1
2
3
4
5
6
7
8
9
10
11
12
/** 根据参数的类型,推断出返回值的类型也是 number */
function add1(a: number, b: number) {
return a + b
}
const x1 = add1(1, 1) // 推断出 x1 的类型也是 number

/** 推断参数 b 的类型是数字或者 undefined,返回值的类型也是数字 */
function add2(a: number, b = 1) {
return a + b
}
const x2 = add2(1)
const x3 = add2(1, '1') // ts(2345) Argument of type "1" is not assignable to parameter of type 'number | undefined

但!如果定义的时候没有赋值,不管之后有没有赋值,都会被推断成 any 类型而完全不被类型检查。

类型断言

TypeScript 类型检测无法做到绝对智能,毕竟程序不能像人一样思考。有时会碰到我们比 TypeScript 更清楚实际类型的情况,比如下面的例子:

1
2
3
4
5
6
7
const arrayNumber: number[] = [1, 2, 3, 4]
const greaterThan2: number = arrayNumber.find((num) => num > 2) // 提示 ts(2322),有可能是 undefinded 或 number

// 加入类型检测

const arrayNumber: number[] = [1, 2, 3, 4]
const greaterThan2: number = arrayNumber.find((num) => num > 2) as number // ok
语法
1
2
3
4
5
6
let str: string = "我是string"
// 相当于跳过类型检测
// 01 尖括号
let a: number = <number>str;
// 02 as 语法
let a: number = str as number;
非空断言

在上下文中当类型检查器无法断定类型时,可以使用 "!"断言操作对象非 null 或 undefined

1
2
3
4
5
6
7
8
9
10
// 示例1
let str: null | undefined | string;
str.toString() // error
str!.toString() // ok
// 示例2
type Foo = () => number
function myFun(foo: Foo || undefined) {
foo();// error
foo!(); //ok
}
确定赋值断言

允许在实例属性和变量声明后面放置一个 "!" 号,从而告诉 TypeScript 该属性会被明确地赋值。为了更好地理解它的作用,我们来看个具体的例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
let x: number;
initialize();

// Variable 'x' is used before being assigned.(2454) 很明显该异常信息是说变量 x 在赋值前被使用
console.log(2 * x); // Error
function initialize() {
x = 10;
}

// 解决该问题,我们可以使用确定赋值断言:
let x!: number;
initialize();
console.log(2 * x); // Ok

function initialize() {
x = 10;
}

通过 let x!: number; 确定赋值断言,TypeScript 编译器就会知道该属性会被明确地赋值。

类型拓宽(Type Widening)

类型缩小(Type Narrowing)

03 编译选项 tsconfig.json💕

编译选项

include

示例: [path1,path2…]

作用:规定需要编译的路径

exclude

示例: [path1,path2…]

作用:排除规定不需要编译的路径

compilerOptions

常见的编译选项:

选项 类型 默认 描述
target string “ES3” 指定ECMAScript目标版本
module string target === “ES6” ? “ES6” : “commonjs” 指定生成哪个模块系统代码
lib string[] 编译过程中需要引入的库文件的列表。
outDir string 重定向输出目录。
outFile string 将输出文件合并为一个文件。
removeComments boolean false 编译后移除注释
allowJs boolean false 是否编译 js 文件
checkJs boolean false .js文件中报告错误。与 --allowJs配合使用。
noEmitOnError boolean false 编译,但不生成编译后的文件
noEmitOnError boolean false 报错时不生成输出文件
alwaysStrict boolean false 以严格模式解析并为每个源文件生成 "use strict"语句
noImplicitAny boolean false 在表达式和声明上有隐含的 any类型时报错。

03 使用 webpack 打包 ts 代码 📦

webpack 配置

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
const path = require('path')
// HTML模板, 自动将打包好的模块得引入到 html内
const HtmlWebpackPlugin = require('html-webpack-plugin')
// 自动清理 dist 文件目录下的老文件
const { CleanWebpackPlugin } = require('clean-webpack-plugin')

module.exports = {
// 开发模式下
mode: 'development',
// 入口
entry: './src/index.ts',
// 出口
output: {
filename: 'bundle.js',
path: path.resolve(__dirname, 'dist'),
environment: {
// 用于兼容 IE 浏览器
arrowFunction: false,
},
},
// 模块
module: {
rules: [
// 用于解析 ts 文件格式规则
{
test: /\.ts$/,
use: [
// 配置 Babel
{
// loder
loader: 'babel-loader',
// 设置 babel
options: {
// 设置预定义得环境
presets: [
[
// 指定环境得插件
'@babel/preset-env',
// 配置信息
{
// 需要兼容目标浏览器
targets: {
ie: 11,
},
// 指定core.js的版本
corejs: '3',
// 使用corejs方式 "usage",表示按需加载
useBuiltIns: 'usage',
},
],
],
},
},
'ts-loader',
],
// 排除的文件
exclude: /node_modules/,
},
],
},

// 配置 Webpack 插件
plugins: [
new CleanWebpackPlugin(),
new HtmlWebpackPlugin((options = { template: './src/index.html' })),
],

// 配置 webpack-dev-server
devServer: {
static: {
directory: path.resolve(__dirname, 'public'),
},
port: 8081,
},

// 用来设置引用模块
resolve: {
// 扩展 ts 可以使用模块
extensions: ['.ts', '.js'],
},
}