一、前言
因为公司项目中还没有引入typescript,而且由于自己犯懒哈哈,感觉自己落下很久了,现在对于ts的理解还仅限于类型检查基础功能使用,对于一些高级用法一无所知,所以这次想通过笔记的方式对ts做一次学习总结。学习的时候一定看typescript英文文档,因为typescript中文网上有些东西已经过时了。
二、类型标注
:
用于类型标注
在下面这个例子中,使用了变量、函数参数以及函数返回值的类型标注:
let myName: string = "Alice";
function identity(num: number): number {
return num;
}
三、数据类型
1. 原始类型
1.1 字符串类型(string)
let name: string='xiaoming';
1.2 数字类型(number)
let age: number=10;
1.3 布尔类型(boolean)
let flag: boolean=false;
注意:
The type names
String
,Number
, andBoolean
(starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always usestring
,number
, orboolean
for types. 类型名字 String、 Number 和 Boolean (以大写字母开头)是合法的,但是引用一些特殊的内置类型,这些类型很少出现在代码中。对于类型,总是使用string
,number
, orboolean
。
2.数组类型
Array<T>,T代表数组中的元素类型。
let arr1: number[] = [1, 2, 3];
let arr2: Array<number> = [4, 5, 6];
3.Tuple 元组类型
Tuple 类型是另一种 Array 类型,它确切地知道它包含多少个元素,以及它在特定位置包含哪些类型,访问跨界元素时,ts会报错
const tuple: [number, string] = [2022, 'hello world']
4.Enum 枚举类型
4.1 普通枚举
enum Gender{
FEMALE, // 0
MALE // 1
}
//简单的使用枚举类型
let User = Gender.FEMALE
console.log(User) // 0 输出结果默认是数字0
注意: 默认情况下,第一个枚举值是 0
,然后每个后续值依次递增 1。
可以通过特定的赋值来改变给任何枚举成员关联的数字:
enum Gender{
FEMALE = 2, // 2
MALE // 3
}
枚举类型编译后是什么样呢?我们看下面的示例:
// 枚举类型
enum Gender{
FEMALE,
MALE
}
// 编译后
var Gender;
(function (Gender) {
Gender[Gender["FEMALE"] = 0] = "FEMALE";
Gender[Gender["MALE"] = 1] = "MALE";
})(Gender || (Gender = {}));
我们看 Gender[Gender["FEMALE"] = 0] = "FEMALE"
这行,Gender["FEMALE"] = 0
的意思是将 Gender
对象里的 FEMALE
成员值设置为 0
,因此得到 Gender[0] = "FEMALE"
。这样你可以使用 Gender
变量进行反向映射,如下所示:
enum Gender{
FEMALE,
MALE
}
console.log(Gender[0]); // 'FEMALE'
console.log(Gender['FEMALE']); // 0
console.log(Gender[Gender.FEMALE]); // 'FEMALE' 因为 Gender.FEMALE == 0
4.2 常量枚举
使用 const 枚举
const enum Gender {
FEMALE,
MALE
}
let genders = Gender.FEMALE;
// 编译后
var genders = 0 /* FEMALE */;
对比上面普通枚举的编译,发现常量枚举编译后对于Gender没有了,只有下面声明的变量 genders = 0。
5. any 任意类型
any
类型在 TypeScript 类型系统中占有特殊的地位。它提供给你一个类型系统的「后门」,TypeScript 将会把类型检查关闭。在类型系统里 any
能够兼容所有的类型(包括它自己)。因此,所有类型都能被赋值给它,它也能被赋值给其他任何类型。
let power: any;
// 赋值任意类型
power = '123';
power = 123;
// 它也兼容任何类型
let num: number;
power = num;
num = power;
6.null 和 undefined
在类型系统中,JavaScript 中的 null 和 undefined 字面量和其他被标注了 any
类型的变量一样,都能被赋值给任意类型的变量。示例如下:
// strictNullChecks: false
let num: number;
let str: string;
// 这些类型能被赋予
num = null;
str = undefined;
注意: strictNullChecks
严格空检查,在tsconfig.json
文件中进行配置,这里如果配置成true
的话,上面示例就会报错,就不能把 null
和 undefined
赋值给其他类型,它们只能赋值给自己这种类型或者 any
。
7. never类型
never 代表不会出现的值。
-
一个从来不会有返回值的函数(如:如果函数内含有
while(true) {}
);function loop():never{ while(true){} }
-
一个总是会抛出错误的函数(如:
function error() { throw new Error('报错了') }
,error
的返回类型是never
);function error():never{ throw new Error('报错了') }
strictNullChecks
-
在 TS 中, null 和 undefined 是任何类型的有效值,所以无法正确地检测它们是否被错误地使用。于是 TS 引入了 strictNullChecks 这一种检查模式
-
由于引入了 --strictNullChecks ,在这一模式下,null 和 undefined 能被检测到。所以 TS 需要一种新的底部类型( bottom type )。所以就引入了 never。
看一个示例:
function foo(x: string | number) { if (typeof x === 'string') { console.log(x) // x 是 string类型 } else if (typeof x === 'number') { console.log(x) // x 是 number类型 }else{ console.log(x) // 这里的x是never类型,因为永远走不到这里 // strictNullChecks 模式下,这里的代码将不会被执行,x 无法被观察 } }
8. void 类型
void 来表示一个函数没有一个返回值
function log(message: string): void {
console.log(message);
// 返回值 void 的时候,它的非严格模式(strictNullChecks:false)下仅可以返回 null 和 undefined
//严格模式(strictNullChecks:true)下只能返回undefined
//return null;
//return undefined;
}
严格模式下 (strictNullChecks:true)
,如果返回 null 会报错 不能将类型“null”分配给类型“void”
。
void 与 never 的区别
void
表示没有任何类型,never
表示永远不存在的值的类型。
当一个函数返回空值时,它的返回值为 void
类型,但是,当一个函数永不返回时(或者总是抛出错误),它的返回值为 never
类型。void
类型可以被赋值(在 strictNullChecking
为 false
时),但是除了 never
本身以外,其他任何类型不能赋值给 never
。
9.Symbol
- Symbol 是在ES2015之后成为新的原始类型,它通过 Symbol 构造函数创建
- Symbol 的值是唯一不变的
let sym2 = Symbol("key");
let sym3 = Symbol("key");
console.log(sym2 === sym3) // false, symbols are unique
10. BigInt
-
使用 BigInt 可以安全地存储和操作大整数
const max = Number.MAX_SAFE_INTEGER;// 最大数字 2**53-1 console.log(max + 1 === max + 2); // true 因为最大数字再加溢出了
-
我们在使用
BigInt
的时候,必须添加ESNext
的编译辅助库 -
要使用
1n
需要"target": "ESNext"
,否则报目标低于 ES2020 时,bigInt 文本不可用。
const max = BigInt(Number.MAX_SAFE_INTEGER); console.log(max + 1n === max + 2n); // false
-
number 和 bigint 类型不一样,不兼容。JS 里的类型Number BigInt,ts里的类型 number bigint
let foo: number; let bar: bigint; foo = bar; bar = foo;
11.包装对象 Wrapper Object
- JavaScript 的类型分为两种:原始数据类型(Primitive data types)和对象类型(Object types)。
- 所有的原始数据类型都没有属性(property)
- 原始数据类型
- 布尔值
- 数值
- 字符串
- null
- undefined
- Symbol
如上所述,在 JavaScript 中字符串被视为原始数据类型而不是对象类型,但是我们来看下面一段代码。
let name = 'lili';
console.log(name.toUpperCase()); // LILI
这里很明显可以看出 name 有一个 toUpperCase 的属性(property)。那么是否上述结论不正确呢?如果 name 不是对象类型,为什么它具有 toUpperCase,toLowerCase 等属性呢?
先说一下结论:
当调用基本数据类型方法的时候,JavaScript 会在原始数据类型和对象类型之间做一个迅速的强制性切换
当我们试图访问 name 的 toUpperCase 属性时,JavaScript 会通过 new String(name) 来强制将字符串的值转换为一个对象类型,这个对象就是包装对象(wrapper object)。
它继承了 string 的所有方法,并且被用于获取其属性(property)上的引用(reference)。一旦属性被调用后,这个包装对象就会被废弃。
let name = (new String('lili')).toUpperCase();
11.1 TypeScript 中类型声明时需要关注的包装对象问题
let isOK: boolean = true; // 编译通过
let isOK: boolean = Boolean(1) // 编译通过
// 不能将类型“Boolean”分配给类型“boolean”。
// “boolean”是基元,但“Boolean”是包装器对象。如可能首选使用“boolean”。
let isOK: boolean = new Boolean(1); // 编译失败
第三行代码会编译出错,这是为什么呢?有了上面提到的包装对象概念,相信大家也就能理解了,因为通过 new Boolean()
生成的值实际上是一个包装对象,并非原始数据类型。此处我们期望的 isOK 是一个原始数据类型,所以 TypeScript 会编译失败。
12.对象类型(Object Types)
除了基本类型之外,最常见的类型是对象类型。这指的是任何带有属性的 JavaScript 值,几乎全部都是属性!要定义对象类型,只需列出其属性及其类型。
比如这里有一个函数,它接受一个点状对象:
function printCoord(pt: { x: number; y: number }) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 3, y: 7 });
上面用一个具有两个属性(x 和 y)的类型对参数进行注释,这两个属性都是 number 类型。
12.1 可选属性
对象类型还可以指定其部分或全部属性是可选的,在属性后面加?
function printName(obj: { first: string; last?: string }) {
// ...
}
// Both OK
printName({ first: "Bob" });
printName({ first: "Alice", last: "Alisson" });
如果你访问一个不存在的属性,你会得到一个undefined
,而不是一个运行时错误。
function printName(obj: { first: string; last?: string }) {
console.log(obj.last.toUpperCase());// Error - might crash if 'obj.last' wasn't provided!
if (obj.last !== undefined) {
console.log(obj.last.toUpperCase()); // OK
}
}
13.联合类型(Union Types)
-
联合类型是由两个或多个其他类型组成的类型,表示可能是这些类型中任何一个类型的值
function printId(id: number | string) { console.log("Your ID is: " + id); } // OK printId(101); // OK printId("202"); // Error printId({ myID: 22342 });
-
未赋值时联合类型上只能访问两个类型共有的属性和方法
下面的例子,联合类型是
number | string
,那不能使用只能 string 使用的方法:function printId(id: number | string) { console.log(id.toUpperCase()); // 报错 // Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'.Property 'toUpperCase' does not exist on type 'string | number'. Property 'toUpperCase' does not exist on type 'number'. }
解决方法通过窄化(narrow)将类型缩小
function printId(id: number | string) { if (typeof id === "string") { // In this branch, id is of type 'string' console.log(id.toUpperCase()); } else { // Here, id is of type 'number' console.log(id); } }
14.字面类型(Literal Types)
const someStr = "abc"
// 相当于:
const someStr: "abc"
// someStr的类型是 "abc",它的值只能是abc
// 同理
const foo = 1
// 相当于:
const foo: 1
// foo 的类型是1(而不是整数)。
// 如果用typeof 操作符
typeof someStr // 'string'
typeof foo // 1
// 对于let
let foo = 1 // foo : number
字面类型配和联合类型:
type Direction = 'Up' | 'Down' | 'Left' | 'Right';
function move(direction: Direction) {
// ...
}
move("Up"); // 这里只能传 'Up' | 'Down' | 'Left' | 'Right' 这4个值中的一个
14.1 可能遇到的问题
定义一个 handleRequest 接口请求的方法,注意这里method参数
function handleRequest(url : string, method : "GET" | "POST") {
// do...
}
// 定义对象
const req = { url: "https://example.com", method: "GET" };
handleRequest(req.url, req.method);
// Error : Argument of type 'string' is not assignable to parameter of type '"GET" | "POST"'.
会发现上面 值传 req.method 会报错,这是因为:
req.method
被推断为 string
,而不是 “GET”
。因为代码可以在 req
的创建和 handleRequest
的调用之间进行计算,handleRequest
可以为 req.method
分配一个新字符串,比如“GUESS”
,所以 TypeScript 认为这段代码有错误。
解决方式:
- 可以通过在任一位置添加类型断言来更改推断:
// 1 const req = { url: "https://example.com", method: "GET" as "GET" }; // 2 handleRequest(req.url, req.method as "GET");
- 可以使用 const 将整个对象转换为文本类型
const req = { url: "https://example.com", method: "GET" } as const
15. 类型别名(Type Aliases)
类型别名的语法是用type
关键字
type Point = {
x: number;
y: number;
};
function printCoord(pt: Point) {
console.log("The coordinate's x value is " + pt.x);
console.log("The coordinate's y value is " + pt.y);
}
printCoord({ x: 100, y: 100 });
也可以命名联合类型:
type StrOrNum = string | number;
// 使用
let sample: StrOrNum;
sample = 123;
sample = '123';
// 会检查类型
sample = true; // Error
四、类型推断
TypeScript 能根据一些简单的规则推断(检查)变量的类型
1.变量的类型,由定义推断:
let foo = 123; // foo 是 'number'
let bar = 'hello'; // bar 是 'string'
foo = bar; // Error: 不能将 'string' 赋值给 `number`
2.函数返回类型
返回类型能被 return
语句推断,如下所示,推断函数返回为一个数字
function add(a: number, b: number) {
return a + b;
}
3.赋值
函数参数类型/返回值也能通过赋值来推断。如下所示,foo
的类型是 Adder
,他能让 foo
的参数 a
、b
是 number
类型。
type Adder = (a: number, b: number) => number;
let foo: Adder = (a, b) => a + b;
4. 结构化
这些简单的规则也适用于结构化的存在(对象字面量),例如在下面这种情况下 foo
的类型被推断为 { a: number, b: number }
const foo = {
a: 123,
b: 456
};
foo.a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
数组同理:
const bar = [1, 2, 3];
bar[0] = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
5.解构
const foo = {
a: 123,
b: 456
};
let { a } = foo;
a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
数组同理:
const bar = [1, 2];
let [a, b] = bar;
a = 'hello'; // Error:不能把 'string' 类型赋值给 'number' 类型
6.noImplicitAny
选项 noImplicitAny
用来告诉编译器,当无法推断一个变量时发出一个错误(或者只能推断为一个隐式的 any
类型),你可以:
- 通过显式添加
:any
的类型注解,来让它成为一个any
类型; - 通过一些更正确的类型注解来帮助 TypeScript 推断类型。
五、类型断言(Type Assertions)
类型断言告诉编译器,我们自己确切的知道变量的类型,而不需要进行类型检查
比如,TypeScript 只知道这会返回某种类型的 HTMLElement
,但是您可能知道您的页面将始终有一个带有给定 ID 的 HTMLCanvasElement
:
const myCanvas =
// HTMLElement
document.getElementById("main_canvas") as HTMLCanvasElement;
但是有的类型断言TS会拒绝,比如:
const x = 'hello' as number
TS会报一个这样的错误:
Conversion of type 'string' to type 'number' may be a mistake because neither type sufficiently overlaps with the other. If this was intentional, convert the expression to 'unknown' first.
当然有时候你可以用any as T
来“欺骗”TS,这个方式也是双重断言
const a = (expr as any) as T;