# TypeScript 基础
《深入理解TypeScript》 (opens new window)
TS Platground (opens new window)
# 语言类型
强类型:不允许随意的隐式类型转换
弱类型:允许隐式类型转换(1+ '2'
)
静态类型:声明过后,它的类型就不允许再修改
动态类型:变量的类型可以随时改变(a=1; a='1'
)
基于以上,所有编程语言分为四类,JS即是弱类型也是动态类型,例如:
const obj = {}
obj.foo() //这样写没问题,只有等到运行时才会报错
// 如果加了一个时间
setTimeout(() => {
obj.foo()
},10000) // 错误就会保留在代码中
// 又或者
function sum (a, b) {
return a + b
}
console.log(sun(100, '100')) //100100
// 还有对象键会自动转化成字符串
obj[true] = 100
console.log(obj['true'])
# TS是静态类型+弱类型
- 错误更早暴露
- 代码更智能,编码更准确
- 重构更牢靠
- 减少不必要的类型判断
# TypeScript项目
yarn init
:初始化package.json
yarn add typescript --dev
:安装typescript
新建ts文件demo.ts
yarn tsc demo.ts
:编译demo.ts
根目录下出现demo.js
,已经自动转化为ES5
# 编译上下文
编译上下文算是一个比较花哨的术语,可以用它来给文件分组,告诉 TypeScript 哪些文件是有效的,哪些是无效的。除了有效文件所携带信息外,编译上下文还包含有正在被使用的编译选项的信息。定义这种逻辑分组,一个比较好的方式是使用 tsconfig.json
文件。
yarn tsc --init
:生成tsconfig.json
"sourceMap": true,
"outDir": "dist",
"rootDir": "src",
创建src
文件夹放ts文件,运行yarn tsc
编译整个项目
# 声明空间
在 TypeScript 里存在两种声明空间:类型声明空间与变量声明空间。
interface Bar {}
const bar = Bar; // Error: "cannot find name 'Bar'"
// 因为 Bar 定义在类型声明空间中,而未定义在变量声明空间中。
const foo = 123;
let bar: foo; // ERROR: "cannot find name 'foo'"
// foo 定义在变量声明空间中,不能用作类型注解。
# 模块
在默认情况下,当你开始在一个新的 TypeScript 文件中写下代码时,它处于全局命名空间中。如在 foo.ts 里的以下代码可以在 bar.ts 中使用。
// foo.ts
const foo = 123;
// bar.ts
const bar = foo; // allowed
所以需要使用文件模块:
// foo.ts
export const foo = 123;
// bar.ts
import { foo } from './foo';
const bar = foo; // allow
# TypeScript类型系统
# 类型注解
类型注解使用 :TypeAnnotation
语法。在类型声明空间中可用的任何内容都可以用作类型注解。例如:
// 函数声明
function func1 (a: number, b?:number): string {
return 'func1'
}
// 函数表达式
const func2: (a: number, b:number) => string = function () {
return 'func2'
}
# 原始类型
string
、number
、boolean
let num: number;
let str: string;
let bool: boolean;
num = 123;
num = 123.456;
num = '123'; // Error
str = '123';
str = 123; // Error
bool = true;
bool = false;
bool = 'false'; // Error
# 特殊类型
any
、 null
、 undefined
以及 void
# any
any
类型在 TypeScript 类型系统中占有特殊的地位。它提供给你一个类型系统的「后门」,TypeScript 将会把类型检查关闭。在类型系统里 any
能够兼容所有的类型(包括它自己)。因此,所有类型都能被赋值给它,它也能被赋值给其他任何类型。
let power: any;
// 赋值任意类型
power = '123';
power = 123;
// 它也兼容任何类型
let power: any;
let num: number;
power = '123'
num = power
// 反过来也可
num = 123
power = num
# null 和 undefined
默认情况下它们是所有类型的子类型,即可以赋值给任意类型
let num: number;
let str: string;
// 这些类型能被赋予
num = null;
str = undefined;
# void
使用 :void
来表示一个函数没有一个返回值
function log(message: string): void {
console.log(message);
}
# 数组
// 两种写法,常使用第二种
const arr1: Array<number> = [1, 2, 3]
const arr2: number[] = [1, 2, 3]
let boolArray: boolean[];
boolArray = [true, false];
console.log(boolArray[0]); // true
console.log(boolArray.length); // 2
boolArray[1] = true;
boolArray = [false, false];
boolArray[0] = 'false'; // Error
boolArray = 'false'; // Error
boolArray = [true, 'false']; // Error
# 对象
除了 Object
之外,函数和数组也属于对象
const obj: { foo: number } = { foo:123 }
const foo: object = function () {}
const bar: object = []
# 接口
为什么需要接口?可以看这个例子:
function print(obj: { a: string }) {
console.log(obj.a);
}
let myObj = { a: '10', b: 20 };
print(myObj);
类型检查器会查看print
的调用。 print
有一个参数,并要求这个对象参数有一个名为a
类型为string
的属性。 需要注意的是,我们传入的对象参数实际上会包含很多属性,但是编译器只会检查那些a
是否存在,而没有检查b
接口可以合并众多类型声明至一个类型声明,在这里,我们把类型注解:a: string
+ b: number
合并到了一个新的类型注解 Obj
里,这样能强制对每个成员进行类型检查。
interface Obj {
a: string;
b: number;
}
let obj: Obj;
obj = {
a: '10',
b: 20
};
// Error: Property 'b' is missing
obj = {
a: '10'
};
// Error: Property 'b' is missing
obj = {
a: '10',
b: '20'
};
# 内联类型注解
内联类型能为你快速的提供一个类型注解。它可以帮助你省去为类型起名的麻烦(你可能会使用一个很糟糕的名称)。然而,如果你发现需要多次使用相同的内联注解时,那么考虑把它重构为一个接口(或者是 type alias
)
let foo: {
first: string;
second: string;
};
foo = {
first: 'John',
second: 'Doe'
};
foo = {
// Error: 'Second is missing'
first: 'John'
};
foo = {
// Error: 'Second is the wrong type'
first: 'John',
second: 1337
};
# 泛型
在计算机科学中,许多算法和数据结构并不会依赖于对象的实际类型。但是,你仍然会想在每个变量里强制提供约束。
function createNumberArray (length: number, value: number): number[] {
const arr = Array<number>(length).fill(value)
return arr
}
function createStringArray (length: number, value: string): string[] {
const arr = Array<string>(length).fill(value)
return arr
}
// 泛型解决冗余问题,在函数后面加 <T>
function createArray<T> (length: number, value: T): T[] {
const arr = Array<T>(length).fill(value)
return arr
}
const res = createArray<string>(3, 'foo')
具体例子请看泛型 (opens new window)
# 联合类型(|)
在 JavaScript 中,你可能希望属性为多种类型之一,如字符串或者数组。它使用 |
作为标记,如 string | number
function formatCommandline(command: string[] | string) {
let line = '';
if (typeof command === 'string') {
line = command.trim();
} else {
line = command.join(' ').trim();
}
}
# 交叉类型(&)
交叉类型是将多个类型合并为一个类型。 这让我们可以把现有的多种类型叠加到一起成为一种类型,它包含了所需的所有类型的特性。
interface A {
name: string;
sex: number;
}
interface B {
age: number;
sex: number;
}
let c: A&B = { name: 'xxx', age: 18, sex: 1 }
# 元组类型
元组可以看作是数组的拓展,它表示已知元素数量和类型的数组。确切地说,是已知数组中每一个位置上的元素的类型。
let nameNumber: [string, number];
// Ok
nameNumber = ['Jenny', 221345];
// Error
nameNumber = ['Jenny', '221345'];
// 与解构一起使用
let nameNumber: [string, number];
nameNumber = ['Jenny', 322134];
const [name, num] = nameNumber;
# 类型别名
TypeScript 提供了为类型注解设置别名的便捷语法,你可以使用 type SomeName = someValidTypeAnnotation
来创建别名:
type StrOrNum = string | number;
// 使用
let sample: StrOrNum;
sample = 123;
sample = '123';
// 会检查类型
sample = true; // Error
# 枚举类型
枚举(Enum)类型用于取值被限定在一定范围内的场景,比如一周只能有七天,颜色限定为红绿蓝等。默认情况下,枚举是基于 0 的,也就是说第一个值是 0,后面的值依次递增。
// 例如文章对象
const post = {
title: 'aaaaa'
content: 'abbb',
status: 0 // 0, 1, 2 代表不同状态,但是不直观
}
// 枚举类型
const enum PostStatus {
Draft = 0,
Unpublished = 1,
Published = 2
}
const post = {
title: 'aaaaa',
content: 'abbb',
status: PostStatus.Draft
}
枚举是基于 0 的,后面的值依次递增。
enum Color {Red, Green, Blue}
let c: Color = Color.Green; // 1
enum Color {Red = 1, Green, Blue = 4}
let c: Color = Color.Green; // 2
# 类
class Person {
public name: string
private age: number
protected readonly gender: boolean // protected 只允许子类访问 readonly 只读属性
constructor (name: string, age: number, gender: boolean) {
this.name = name
this.age = age
this.gender = gender
}
sayHi (msg: string): void {
console.log(this.name, msg)
}
}
class Student extends Person {
constructor (name: string, age: number, gender: boolean) {
super(name, age, gender)
console.log(gender) // 只有在这里能访问 gender
}
}
# 用类实现接口
实现(implements)是面向对象中的一个重要概念。一般来讲,一个类只能继承自另一个类,有时候不同类之间可以有一些共有的特性,这时候就可以把特性提取成接口(interfaces),用
implements
关键字来实现。这个特性大大提高了面向对象的灵活性。
interface Eat {
eat(food: string): void
}
interface Run {
run(distance: number): void
}
class Person implements Eat, Run {
eat (food: string): void {
console.log(`优雅地进餐:${food}`)
}
run (distance: number): void {
console.log(`直立行走:${distance}`)
}
}
class Animal implements Eat, Run {
eat (food: string): void {
console.log(`呼噜呼噜地吃:${food}`)
}
run (distance: number): void {
console.log(`爬行:${distance}`)
}
}
# 抽象类
抽象类作为其他派生类的基类使用,它们一般不会直接被实例化,不同于接口,抽象类可以包含成员的实现细节。abstract
关键字是用于定义抽象类和在抽象类内部定义抽象方法。抽象类中的抽象方法不包含具体实现并且必须在派生类中实现。
abstract class Animal { // 抽象类只能被继承
eat (food: string): void {
console.log(`呼噜呼噜地吃:${food}`)
}
abstract run (distance: number): void // 抽象方法在子类中必须实现
}
class Dog extends Animal {
run (distance: number): void {
console.log('爬行:' + distance);
}
}
const d = new Dog();
d.eat('food')
d.run(100)
# 高级类型
# partial
Partial 形容词,译为 部分的/局部的/不完全的。使一个 interface/class 或 其他 k/v 形式的 类型的每一个属性完全可选。
类型声明:
/**
* Make all properties in T optional
*/
type Partial<T> = {
[P in keyof T]?: T[P];
};
示例:
interface A {
id: number;
name: string;
age: number;
}
type PartialA = Partial<A>;
# Record
Record<K,T>
构造具有给定类型T
的一组属性K
的类型。在将一个类型的属性映射到另一个类型的属性时,Record
非常方便。(是一个对象类型)
类型声明:
/**
* Construct a type with a set of properties K of type T
*/
type Record<K extends keyof any, T> = {
[P in K]: T;
};
示例:
interface EmployeeType {
id: number
fullname: string
role: string
}
let employees: Record<number, EmployeeType> = {
0: { id: 1, fullname: "John Doe", role: "Designer" },
1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
2: { id: 3, fullname: "Sara Duckson", role: "Developer" },
}
// 0: { id: 1, fullname: "John Doe", role: "Designer" },
// 1: { id: 2, fullname: "Ibrahima Fall", role: "Developer" },
// 2: { id: 3, fullname: "Sara Duckson", role: "Developer" }
# Pick
从 T 中,选择一组键在并集 K 中的属性。实际就是说在原有的类型 T 上 筛选出想要的全部或极个别的属性和类型。
类型声明:
/**
* From T, pick a set of properties whose keys are in the union K
*/
type Pick<T, K extends keyof T> = {
[P in K]: T[P];
};
示例:
interface B {
id: number;
name: string;
age: number;
}
type PickB = Pick<B, "id" | "name">;
# Omit
作用与Pick相反,Omit是排除一个字段,剩下的所有,
interface C {
id: number;
name: string;
age: number;
}
type OmitC = Omit<C, "id">;