目录
1.概述
TypeScript(TS)是一种由微软开发的开源编程语言,是JavaScript的超集。它的主要特点包括:
-
类型系统:TypeScript引入了静态类型,可以在编译时检查类型错误,这有助于提高代码的可靠性和可维护性。
-
现代JavaScript特性:TypeScript支持ES6及更新版本的所有特性,包括箭头函数、类、模块等。
-
工具支持:TypeScript与许多编辑器和IDE(如Visual Studio Code)有良好的集成,提供智能感知、重构、导航等功能。
-
兼容性:TypeScript代码可以编译成普通的JavaScript,因此可以在任何支持JavaScript的环境中运行。
-
面向对象编程:TypeScript增强了JavaScript的面向对象编程能力,支持类、接口和继承等概念。
2.类型注解
用来显式指定变量、参数或返回值类型的一种方式。
let num = 123;
num = 'hello'; //不报错
let num:number = 123;
num = 'hello'; //报错,不能将类型'string'分配给类型'number'
类型注解提供了一种方式来为 TypeScript 编译器提供关于代码中数据类型的信息,有助于编译器进行类型检查和自动补全。
为什么使用类型注解:
-
类型安全:类型注解帮助 TypeScript 编译器捕获错误,如类型不匹配、错误的属性访问等。
-
代码可读性:通过类型注解,其他开发者可以更容易地理解代码的预期行为。
-
自动补全:在许多集成开发环境(IDE)中,类型注解可以提供更好的代码自动补全和提示。
-
重构:类型注解使得重构代码更加安全,因为编译器会检查类型错误。
3.基本类型
Boolean类型
let flag: boolean = false;
// ES5:var flag = false;
Number类型
const num: number = 10;
// ES5:var num = 10;
String类型
let str: string = "hello";
// ES5:var str = 'hello';
Array类型
-
类型后跟数组标记
[]
:这种方式表示一个由特定类型组成的数组。 -
泛型数组类型
Array<元素类型>
:使用泛型来定义数组类型。
let arr1: number[] = [1, 2, 3]; // 显式声明数组元素类型
// ES5:var arr1 = [1, 2, 3];
let arr2: Array<number> = [1, 2, 3]; // 使用泛型定义数组元素类型
// ES5:var arr2 = [1, 2, 3];
Enum类型
enum Color {
Red,
Green,
Blue
}
let c: Color = Color.Green;
默认情况下,枚举成员的值是从 0
开始自动递增的。
Any类型
一个变量如果被声明为
any
类型,那么它可以被赋予任何值,包括null
和undefined
,并且可以调用其上的任何属性和方法,而不会报错。
赋值任何值
let value: any = 42; // number
value = "Hello World"; // string
value = true; // boolean
value = { key: "value" }; // object
value = [1, 2, 3]; // array
value = { x: 10, y: 20 }; // object with x and y properties
调用任何属性和方法
let value: any;
value.toUpperCase(); // 假设 value 是一个字符串
value.toFixed(2); // 假设 value 是一个数字
value.doSomething(); // 假设 value 有一个 doSomething 方法
Tuple类型
元组(Tuple)类型是一种已知数量和类型的数组,各元素的类型不必相同。
let x: [string, number];
// 正确
let x: [string, number] = ["hello", 10];
// 错误,类型不匹配
let x: [string, number] = [10, "hello"];
元组的局限性
-
元组的大小是固定的,一旦声明,你不能添加或删除元素。
-
元组的类型是静态的,这意味着你不能在运行时改变元素的类型。
元组与数组的区别
-
数组:数组中的元素类型必须相同。
void类型
void
类型与any
类型恰好相反。它表示没有任何类型,通常用作那些不返回任何值的函数的返回类型。
function calculateSum(a: number, b: number): void {
console.log(a + b);
}
Null&Undefined
-
null
是一个表示故意的空值的特殊值,它表示变量有一个空的引用,即变量指向一个空对象。 -
undefined
表示变量被声明了,但没有被赋值。
let u: undefined = undefined;
let n: null = null;
Never类型
never
类型表示那些永不存在的值的类型。它通常用于那些总是会抛出错误或根本就不会有返回值的函数表达式或箭头函数的返回值类型。
// 函数永远不会到达返回点,因此返回类型是 never
function error(message: string): never {
throw new Error(message);
}
// 函数不存在返回路径,因此返回类型是 never
function infiniteLoop(): never {
while (true) {
// ...
}
}
never
类型的用途
-
表示永不存在的值:
never
类型用于那些不可能有返回值的函数。 -
类型保护:在高级类型检查中,
never
可以用于类型保护,以确保某些值永远不可能是特定的类型。 -
限制函数返回类型:如果你有一个函数,你希望它永远不会正常返回,可以使用
never
。
never
类型的特性
-
子类型关系:
never
是任何类型的子类型,这意味着可以将never
类型的值赋给任何其他类型的变量。这是有意义的,因为never
类型的值实际不存在。 -
赋值关系:
never
是任何类型的父类型,除了any
和never
本身。这意味着你不能将任何类型的值赋给never
类型的变量。
never
类型与 any
类型的比较
-
any
:可以赋值给任何类型,也可以从任何类型赋值。 -
never
:只能从never
类型赋值,不能赋值给任何类型。
never
类型与 void
类型的比较
-
void
:表示没有值,通常用作函数没有返回值的返回类型。 -
never
:表示永不存在的值,通常用于那些总是会抛出错误或根本不会有返回值的函数。
Unknown类型
unknown
类型是一种顶级类型,它代表任何可能的类型。使用unknown
类型可以提高代码的类型安全性,因为它不允许你对unknown
类型的值进行任何操作,除非你先进行类型检查。
let value: unknown;
unknown
类型的用途
-
类型安全:
unknown
类型用于那些你不确定其类型的变量,它要求你在对变量进行操作之前进行类型检查。 -
避免类型断言:在没有完全确定变量类型的情况下,使用
unknown
类型可以避免不必要的类型断言。 -
类型保护:
unknown
类型可以与类型保护结合使用,以确保变量在赋值前已经通过了类型检查。
unknown
类型的特性
-
类型检查:对
unknown
类型的值进行操作之前,必须先进行类型检查或类型断言。 -
类型兼容性:
unknown
是所有类型的超类型,这意味着你可以将任何类型的值赋给unknown
类型的变量。同时,unknown
也是所有类型的子类型,但当你尝试将unknown
类型的值赋给其他类型的变量时,必须先进行类型检查。
unknown
类型与 any
类型的比较
-
any
:可以赋值给任何类型,也可以从任何类型赋值。对any
类型的值进行操作不需要类型检查。 -
unknown
:可以赋值给任何类型,但对unknown
类型的值进行操作需要类型检查。
字面量类型
字面量类型(Literal Types)是指那些与特定字面量值对应的类型。
let mode: "admin" | "user" | "guest" = "user";
//mode 可以是 "admin"、"user" 或 "guest" 中的任意一个字符串字面量。
let count: 1 | 2 | 3 | 4 | 5 = 3;
count 可以是 1、2、3、4 或 5 中的任意一个数字字面量。
使用字面量类型的用途
-
提供精确的类型信息:字面量类型可以提供比基本类型更精确的类型信息。
-
限制变量的值:你可以使用字面量类型来限制变量可以赋的值。
-
类型保护:字面量类型可以用于类型保护,以确保变量是特定的字面量值。
4.高级类型
联合类型
联合类型(Union Types)是一种将多个类型合并为一个类型的方式。联合类型表示一个值可以是几种类型之一,使用竖线(
|
)分隔每个类型。
type StringOrNumber = string | number;
let myValue: StringOrNumber;
myValue = "Hello"; // OK
myValue = 42; // OK
联合类型的用途
-
表示一个变量可以是多种类型:联合类型可以让你为变量指定多种可能的类型。
-
类型保护:联合类型可以与类型保护结合使用,以确保变量是特定的类型之一。
-
函数重载:联合类型可以用于函数重载,以区分不同的参数类型。
交叉类型
交叉类型(Intersection Types)允许你将多个类型合并为一个类型。这与联合类型相反,联合类型是允许一个值可以是几种类型之一,而交叉类型是允许一个值同时具有几种类型的所有特性。
type Person = {
name: string;
age: number;
};
type Logger = {
log(message: string): void;
};
type LoggedPerson = Person & Logger;
let person: LoggedPerson = {
name: "sunrise",
age: 23,
log(message: string): void {
console.log(`LoggedPerson log: ${message}`);
}
};
交叉类型的用途
-
组合多个类型的特性:当你需要一个类型同时具有多个其他类型的特性时,可以使用交叉类型。
-
增强现有类型:你可以使用交叉类型来增强现有的类型,添加额外的属性或方法。
-
实现多重继承:在 TypeScript 中,交叉类型可以模拟多重继承。
可选类型
可选类型(Optional Types)是指在对象中不强制要求某些属性必须存在的类型。这通过在属性名后添加
?
符号来表示。
type Person = {
name: string;
age: number;
height?: number; // height 是可选属性
};
let person: Person = {
name: "sunrise",
age: 30
// height 属性可以省略
};
let tallPerson: Person = {
name: "TUIUT",
age: 25,
height: 185 // 也可以提供 height 属性
};
可选类型的用途
-
提供灵活性:可选类型允许对象在某些属性缺失的情况下仍然有效。
-
处理不完整数据:在处理可能缺失某些字段的数据时,如从 API 接收的数据,可选类型非常有用。
-
实现默认值:在函数参数中,可以使用可选参数来提供默认值。
条件类型
条件类型(Conditional Types)是一种高级类型功能,它允许你在类型系统中使用条件表达式。
type IfEquals<T, U, X, Y> = T extends U ? X : Y;
type IsString = IfEquals<string, string, "Yes", "No">; // "Yes"
type IsNumber = IfEquals<number, string, "Yes", "No">; // "No"
// IsNumber 是一个条件类型,它检查类型 T 是否为 number 类型,并相应地返回 "Yes" 或 "No"。
条件类型的用途
-
类型守卫:创建复杂的类型保护逻辑,以区分不同的类型。
-
类型映射:基于某个类型的特性生成新的类型。
-
类型转换:根据一个类型生成另一个相关的类型。
映射类型
映射类型(Mapped Types)是一种根据现有类型创建新类型的方法,它允许你通过映射字面量类型或数字类型来创建类型。
type OptionsFlags<T> = {
[P in T]: boolean;
};
type FeatureFlags = OptionsFlags<"login" | "signup" | "logout">;
// { login: boolean; signup: boolean; logout: boolean }
// OptionsFlags 是一个映射类型,它将联合类型 "login" | "signup" | "logout" 中的每个成员映射为一个布尔值。
条件类型的用途
-
类型守卫:创建复杂的类型保护逻辑,以区分不同的类型。
-
类型映射:基于某个类型的特性生成新的类型。
-
类型转换:根据一个类型生成另一个相关的类型。
5.函数
1. 函数类型注解
在函数定义中指定参数类型和返回值类型。
// 无参数,返回 void
function greet(): void {
console.log("Hello, world!");
}
// 一个参数,参数类型为 string,返回值类型也为 string
function repeatMessage(message: string): string {
return message + message;
}
// 多个参数,指定每个参数的类型
function add(a: number, b: number): number {
return a + b;
}
2. 函数类型作为类型别名
定义一个函数类型作为一个类型别名。
// 定义一个函数类型别名
type StringProcessor = (message: string) => string;
// 使用这个类型别名
const repeatMessage: StringProcessor = (message: string) => {
return message + message;
};
3. 可选参数和默认参数
// 可选参数
function greet(name?: string): void {
if (name) {
console.log(`Hello, ${name}!`);
} else {
console.log("Hello, world!");
}
}
// 默认参数
function greetUser(name: string = "World"): void {
console.log(`Hello, ${name}!`);
}
4. 重载
允许你为函数提供多个类型定义(重载),以处理不同的参数组合。
// 函数重载
function sayHello(toWhom: string, greeting?: string): string;
function sayHello(greeting: string, toWhom?: string): string;
// 实现
function sayHello(a: string, b?: string): string {
if (b) {
return `Hello, ${a}! ${b}`;
} else {
return `Hello, ${b}! ${a}`;
}
}
const result1 = sayHello("Alice"); // "Hello, Alice! Hello"
const result2 = sayHello("Hello", "Alice"); // "Hello, Alice! Hello"
高级函数类型
1. 箭头函数
const add: (a: number, b: number) => number = (a, b) => a + b;
2. 函数类型注解
const processArray: {
<T>(arr: T[]): T[];
} = {
<T>(arr: T[]): T[] {
return arr.map(item => item);
}
};
3. this 参数
class Calculator {
public sum(a: number, b: number): number {
return a + b;
}
}
const calculator = new Calculator();
const sum = calculator.sum.bind(calculator);
函数的类型推断
TypeScript 编译器可以自动推断函数返回值的类型。
function createGreeting(greeting: string) {
return () => `${greeting}, world!`;
}
const greet = createGreeting("Hello");
console.log(greet()); // "Hello, world!"
// 编译器推断 `greet` 是一个函数,返回一个字符串。
6.接口
接口是 TypeScript 中定义对象结构的主要方式。它们可以包含属性、方法签名,甚至是索引签名。
定义一个接口
interface Person {
firstName: string;
lastName: string;
age?: number; // 可选属性
greet?(): void; // 可选方法
}
const alice: Person = {
firstName: "Alice",
lastName: "Johnson",
age: 30,
greet() {
console.log(`Hello, my name is ${this.firstName} ${this.lastName}!`);
}
};
alice.greet(); // 输出: Hello, my name is Alice Johnson!
可选属性和方法
接口中的属性可以是可选的,这意味着在实现接口时,这些属性不是必须的。
函数成员
接口也可以描述对象应该拥有哪些方法。
interface Greeting {
greet(name: string): void;
}
const greeter: Greeting = {
greet(name) {
console.log(`Hello, ${name}!`);
}
};
greeter.greet("Alice"); // 输出: Hello, Alice!
对象字面量类型
对象字面量类型是 TypeScript 根据字面量值自动生成的类型。
自动类型推断
const bob = {
firstName: "Bob",
lastName: "Smith",
jobTitle: "Developer" // 额外属性
};
// TypeScript 推断 bob 的类型为:
// {
// firstName: string;
// lastName: string;
// jobTitle?: string;
// }
接口继承
接口可以使用 extends
关键字来扩展其他接口。
interface Named {
name: string;
}
interface Greetable extends Named {
greet(): void;
}
const person: Greetable = {
name: "Alice",
greet() {
console.log(`Hello, my name is ${this.name}!`);
}
};
person.greet(); // 输出: Hello, my name is Alice!
接口与类型别名
接口非常适合定义对象的形状,而类型别名(Type Aliases)则提供了一种方式来为复杂类型命名。
type Point = {
x: number;
y: number;
};
const point: Point = { x: 10, y: 20 };
索引签名
接口可以使用索引签名来定义对象可以有哪些键,以及这些键应该对应什么类型的值。
interface StringArray {
[index: number]: string;
}
const myArray: StringArray = ["Bob", "Fred"];
myArray[0] = "Barbara"; // OK
myArray[1] = 123; // Error
接口与类型兼容性
接口定义了对象的结构,而 TypeScript 的类型系统确保对象符合这些结构。
interface Person {
firstName: string;
lastName: string;
}
const alice: Person = {
firstName: "Alice",
lastName: "Johnson"
};
// 正确,alice 符合 Person 接口
const person: Person = alice;