TypeScript 学习笔记
目录
一、TypeScript 简介
TypeScript 是一种由微软开发和维护的开源编程语言,它在JavaScript的基础上增加了静态类型定义。
- TypeScript 为 JavaScript 添加了类型语法,可以在编辑器中尽早捕捉类型错误。
- TypeScript 代码会转换为 JavaScript,可以在任何运行 JavaScript 的地方运行。
- TypeScript 能够理解 JavaScript,并且无需额外的代码,便能提供类型推断。
// JavaScript:无明确类型
let num = 42
// TypeScript:有明确类型
let num: number = 42
二、TypeScript 基础
1. TypeScript 安装、运行
安装
- 下载 Node.js 并安装(包含npm)
终端输入node -v
npm -v
,验证安装是否成功 - 使用 npm 安装 TypeScript,然后终端输入
tsc -v
验证
npm install -g typescript
编译运行
- 创建 ts 文件
// hello.ts
console.log("Hello, world!");
- 在终端使用 tsc 生成一个与 ts 文件同名的 js文件,再使用 node 执行 js
tsc hello.ts
node hello.js
- 简化过程,使用 ts-node 直接执行 ts
npm i -g ts-node
ts-node hello.ts
2. 基本语法
变量声明
var x = 10; // 全局或函数作用域
let y = 20; // 块级作用域
const z = 30; // 常量,块级作用域
原始类型
let isDone: boolean = false; // 布尔型
let decimal: number = 6; // 数字型,支持十进制、十六进制、二进制和八进制
let hex: number = 0xf00d;
let binary: number = 0b1010;
let octal: number = 0o744;
let color: string = "blue"; // 字符串,可选单双引号,支持模板字符串和嵌入表达式。
color = 'red';
let fullName: string = `Alex Cheng`;
let age: number = 21;
let sentence: string = `Hello, my name is ${fullName}. I'll be ${age + 1} years old next month.`;
let u: undefined = undefined; // 空值
let n: null = null; // 未定义
let sym1 = Symbol(); // Symbol唯一标识符
let sym2 = Symbol("key"); // 可选描述
console.log(sym1); // Symbol()
console.log(sym2); // Symbol(key)
console.log(Symbol("key") === Symbol("key")); // false,Symbol唯一
其他基本类型
let list: number[] = [1, 2, 3]; // 数组,两种方式
let list: Array<number> = [1, 2, 3];
let tuple: [string, number]; // 元组
tuple = ["hello", 10];
// tuple = [10, "hello"]; 错误
enum Color { Red, Green, Blue } // 枚举,默认以0开始编号
//enum Color {Red = 1, Green = 2, Blue = 4}
let c: Color = Color.Green;
let notSure: any = 42; // 任意类型
notSure = "maybe a string";
notSure = false;
function warnUser(): void {
console.log("This is my warning message");
} // 空类型,通常可用于函数返回值
function error(message: string): never {
throw new Error(message);
} // 永不存在的值的类型
类型推断
let message = "Hello, world"; // 推断为string类型
// message = 42; // 错误: number类型不能分配给string类型
类型断言
类型断言是告诉编译器某个值的类型,不让编译器自己推断。
语法:<Type>value
或value as Type
示例:在Web开发中,处理DOM元素时经常需要使用类型断言,因为从DOM中获取的元素默认类型是HTMLElement
,而不是更具体的类型,如HTMLInputElement
、HTMLButtonElement
等。
// 假设 <input id="username" type="text">
let inutElement = document.getElementById('username');
let inputElementAsInput = inputElement as HTMLInputElement;
console.log(inputElementAsInput.value);
三、TypeScript 中的函数
1. 函数的基本定义
函数声明
function add(x: number, y: number): number {
return x + y;
}
函数表达式
const add = function(x: number, y: number): number {
return x + y;
};
箭头函数
const add = (x: number, y: number): number => x + y;
2. 函数的参数
可选参数
function greet(name: string, greeting?: string): string {
if (greeting) {
return `${greeting}, ${name}`;
} else {
return `Hello, ${name}`;
}
}
默认参数
function greet(name: string, greeting: string = 'Hello'): string {
return `${greeting}, ${name}`;
}
剩余参数
function buildName(firstName: string, ...restOfName: string[]): string {
return `${firstName} ${restOfName.join(" ")}`;
}
let NameToBuild = buildName("Alex", "X", "X", "Cheng"); // "Alex X X Cheng"
3. 函数的重载
function double(value: number): number;
function double(value: string): string;
function double(value: any): any {
if (typeof value === 'number') {
return value * 2;
} else if (typeof value === 'string') {
return value + value;
}
}
4. 泛型函数
function identity<T>(value: T): T {
return value;
}
let numberIdentity = identity<number>(42); // 返回 42
let stringIdentity = identity<string>('hello'); // 返回 'hello'
四、接口和类型别名
1. 接口定义和使用
基本接口
interface Person {
name: string;
age: number;
}
let user: Person = {
name: "Alex Cheng",
age: 20
};
可选属性
interface Person {
name: string;
age?: number;
}
let user: Person = {
name: "Alex Cheng"
};
只读属性
interface Point {
readonly x: number;
readonly y: number;
}
let point: Point = { x: 10, y: 20 };
// point.x = 5; // Error: Cannot assign to 'x' because it is a read-only property.
2. 继承接口
基本基础
interface Shape {
shape: string;
}
interface Square extends Shape {
sideLength: number;
}
let square: Square = { shape: "square", sideLength: 42 };
多重继承
interface Shape {
shape: string;
}
interface Color {
color: number;
}
interface Square extends Shape, Color {
sideLength: number;
}
let square: Square = { shape: "square", color: 1, sideLength: 10 };
3. 函数接口
interface SearchFunc {
(source: string, subString: string): boolean;
}
let mySearch: SearchFunc = function(source: string, subString: string): boolean {
return source.indexOf(subString) > -1;
};
console.log(mySearch("Hello,world", "world")); // true
4. 泛型接口
interface KeyValuePair<K, V> {
key: K;
value: V;
}
const pair1: KeyValuePair<string, number> = { key: "age", value: 20 };
const pair2: KeyValuePair<number, string> = { key: 1, value: "one" };
console.log(pair1); // 输出: { key: "age", value: 20 }
console.log(pair2); // 输出: { key: 1, value: "one" }
5. 基本类型别名
type Name = string;
let myName: Name = "Alex";
6. 联合类型和交叉类型
// 联合类型
type StringOrNumber = string | number;
let value: StringOrNumber;
value = "Hello"; // OK
value = 123; // OK
//交叉类型
type PartialPointX = { x: number; };
type PartialPointY = { y: number; };
type Point = PartialPointX & PartialPointY;
let point: Point = { x: 10, y: 20 };
7. 何时使用接口/类型别名
需要定义一个新的对象类型或函数类型时,优先使用接口。
需要使用联合类型、交叉类型或元组时,使用类型别名。
五、类和继承
1. 类的定义
class Person {
name: string;
age: number;
constructor(name: string, age: number) { //构造函数
this.name = name;
this.age = age;
}
greet() {
console.log(`Hello, my name is ${this.name} and I am ${this.age} years old.`);
}
}
const person = new Person('Alex', 20);
person.greet(); // Hello, my name is Alex and I am 20 years old.
2. 继承和派生类
class Student extends Person {
studentId: number;
constructor(name: string, age: number, studentId: number) {
super(name, age); //继承父类的name、age
this.studentId = studentId;
}
displayStudent() {
console.log(`My student ID is ${this.studentId}.`);
}
}
// My student ID is 123456.
3. 实现接口
interface Animal {
name: string;
makeSound(): void;
}
class Dog implements Animal {
name: string;
constructor(name: string) {
this.name = name;
}
makeSound() {
console.log("Woof!");
}
}
const myDog = new Dog("Ben");
myDog.makeSound(); // Woof!
4. 方法重写
class ExchangeStudent extends Student {
constructor(name: string, age: number, studentId: number) {
super(name, age, studentId);
}
greet() {
console.log(`Hello, I am exchange student ${this.name}.`);
}
}
const exchangeStudent = new ExchangeStudent('Alex', 20, 12345);
exchangeStudent.greet(); // Hello, I am exchange student Alex.
exchangeStudent.displayStudent(); // My student ID is 12345.
5. 修饰符
- public:默认修饰符,属性和方法可以在任何地方访问。
- private:属性和方法只能在类内部访问。
- protected:属性和方法可以在类内部及其子类中访问。
class Animal {
public name: string;
private age: number;
protected species: string;
constructor(name: string, age: number, species: string) {
this.name = name;
this.age = age;
this.species = species;
}
protected displayInfo() {
console.log(`Name: ${this.name}, Age: ${this.age}, Species: ${this.species}`);
}
}
class Dog extends Animal {
constructor(name: string, age: number, species: string) {
super(name, age, species);
}
public getSpecies() {
this.displayInfo();
console.log(`Species: ${this.species}`); // 访问受保护的属性
}
}
const dog = new Dog('Ben', 5, 'X');
//dog.displayInfo();// Property 'displayInfo' is protected and only accessible within class 'Animal' and its subclasses.
dog.getSpecies(); // Name: Ben, Age: 5, Species: X
// Species: X
6. 静态成员
静态成员属于类而不是实例,可以通过类名直接访问。
class MathUtils {
static Pi: number = 3.14;
static calculateCircumference(radius: number): number {
return 2 * MathUtils.Pi * radius;
}
}
console.log(MathUtils.Pi); // 3.14
console.log(MathUtils.calculateCircumference(10).toFixed(1)); // 62.8
7. 抽象类
抽象类不能被实例化,只能被继承,用于定义子类的通用结构。
abstract class Vehicle {
abstract move(): void;
start() {
console.log('Vehicle started.');
}
}
class Car extends Vehicle {
move() {
console.log('Car is moving.');
}
}
const car = new Car();
car.start(); // 输出: Vehicle started.
car.move(); // 输出: Car is moving.
8. 泛型类
class Pair<K, V> {
constructor(public key: K, public value: V) {}
display(): void {
console.log(`Key: ${this.key}, Value: ${this.value}`);
}
}
let pair = new Pair("age", 30);
pair.display(); // 输出: Key: age, Value: 30
六、模块和命名空间
1. 模块的导入和导出
// 默认导出(MyClass.ts)
export default class MyClass { ... }
// 导入默认导出(main.ts)
import MyClass from './MyClass';
// 命名导出(myModule.ts)
export const myVar = 123;
export function myFunction() { ... }
export class MyClass { ... }
//或者
const myVar = 123;
function myFunction() { ... }
class MyClass { ... }
export { myVar, myFunction, MyClass }
// 导入命名导出(main.ts)
import { myVar, myFunction, MyClass } from './myModule';
// 重命名导入(main.ts)
import { myVar as newVarName } from './myModule';
2. 命名空间
// 定义
namespace MyNamespace {
export class MyClass { ... }
export function myFunction() { ... }
}
// 使用
let myClassInstance = new MyNamespace.MyClass();
MyNamespace.myFunction();
//定义嵌套命名空间
namespace OuterNamespace {
export namespace InnerNamespace {
export class MyClass { ... }
}
}
//使用嵌套命名空间
let innerClass = new OuterNamespace.InnerNamespace.MyClass();
七、高级类型
1. 字面量
替代枚举类型
限制函数参数和返回值的取值范围
- 字符串字面量类型
type Direction = 'left' | 'right' | 'up' | 'down';
function move(direction: Direction) { }
- 数字字面量类型
type Port = 80 | 443 | 8080;
function startServer(port: Port) { }
- 布尔字面量类型
type YesOrNo = true | false;
function isAdminUser(userType: YesOrNo) { }
2. 类型保护
typeof
类型保护
是通过检查变量的类型来确定其类型的一种方式。它通常用于基本数据类型(如 string
、number
、boolean
、symbol
、undefined
和 object
)的检查。
function padLeft(value: string, padding: string | number) {
if (typeof padding === "number") {
return Array(padding + 1).join(" ") + value;
}
if (typeof padding === "string") {
return padding + value;
}
throw new Error(`Expected string or number, got '${typeof padding}'.`);
}
instanceof
类型保护
用来检查一个对象是否是特定类的实例。它在面向对象编程中非常有用。
class Bird {
fly() {
console.log("The bird is flying.");
}
}
class Fish {
swim() {
console.log("The fish is swimming.");
}
}
function move(animal: Bird | Fish) {
if (animal instanceof Bird) {
animal.fly();
} else if (animal instanceof Fish) {
animal.swim();
} else {
throw new Error("Unsupported animal type");
}
}
in
类型保护
用来检查对象是否具有某个属性名(或者键名)。
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
type Shape = Square | Rectangle;
function area(shape: Shape): number {
if ("size" in shape) {
return shape.size * shape.size;
}
if ("width" in shape && "height" in shape) {
return shape.width * shape.height;
}
throw new Error("Unsupported shape");
}
3. 可辨识联合
// 定义可辨识联合类型
interface Square {
kind: "square";
size: number;
}
interface Rectangle {
kind: "rectangle";
width: number;
height: number;
}
interface Circle {
kind: "circle";
radius: number;
}
// 联合类型
type Shape = Square | Rectangle | Circle;
// 函数示例,根据不同类型计算面积
function calculateArea(shape: Shape): number {
switch (shape.kind) {
case "square": return shape.size * shape.size;
case "rectangle": return shape.width * shape.height;
case "circle": return Math.PI * shape.radius ** 2;
default: return assertNever(shape); // 防止遗漏
}
}
// 辅助函数,确保所有类型都被处理
function assertNever(value: never): never {
throw new Error(`Unhandled value: ${value}`);
}
// 使用示例
const square: Square = { kind: "square", size: 5 };
console.log(calculateArea(square)); // 25
4. 索引类型
索引签名
使用索引签名允许对象中任意属性的类型声明。
示例:{ [key: string]: any; }
// 支持字符串和数字作为索引类型
// 数字型索引(模拟数组)
interface myArray<T> {
[index: number]: T;
}
let arr: myArray<number> = [1, 2, 3];
// 字符型索引
interface myArray1<T> {
[index: string]: T;
}
let arr1: myArray1<number> = { A: 1, B: 2, C: 3 }
索引类型查询操作符(keyof)
keyof
操作符用于获取类型的所有属性名。
示例:type Keys = keyof { foo: number, bar: string }; // "foo" | "bar"
索引访问操作符
[]
操作符允许我们根据类型的索引类型来访问其属性类型。
示例:Person['name']
的类型为 string
interface Person {
name: string;
age: number;
location: string;
}
type NameType = Person['name'];
const person: Person = {
name: "Alice",
age: 30,
location: "New York"
};
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
return obj[key];
}
const name: string = getProperty(person, "name");
const age: number = getProperty(person, "age");
const location: string = getProperty(person, "location");
console.log(name, age, location); // Alice 30 New York
5. 实用类型
(1) Partial<T>
- 定义:将类型
T
的所有属性变为可选属性 - 语法:
type Partial<T> = { [P in keyof T]?: T[P] };
- 示例:
interface User {
id: number;
name: string;
email: string;
}
const updateUser: Partial<User> = {
name: "John Doe"
};
(2) Required<T>
- 定义:将类型
T
的所有属性变为必选属性 - 语法:
type Required<T> = { [P in keyof T]-?: T[P] };
- 示例:
interface User {
id?: number;
name?: string;
email?: string;
}
const user: Required<User> = {
id: 1,
name: "John Doe",
email: "john.doe@example.com"
};
(3) Readonly<T>
- 定义:将类型
T
的所有属性变为只读属性 - 语法:
type Readonly<T> = { readonly [P in keyof T]: T[P] };
- 示例:
interface User {
id: number;
name: string;
email: string;
}
const user: Readonly<User> = {
id: 1,
name: "John Doe",
email: "john.doe@example.com"
};
// user.id = 2; // Error: Cannot assign to 'id' because it is a read-only property.
(4) Record<K, T>
- 定义:构造一个对象类型,其属性键为类型
K
,属性值为类型T
- 语法:
type Record<K extends keyof any, T> = { [P in K]: T };
- 示例:
type Role = "admin" | "user" | "guest";
interface UserInfo {
name: string;
email: string;
}
const users: Record<Role, UserInfo> = {
admin: { name: "Admin", email: "admin@example.com" },
user: { name: "User", email: "user@example.com" },
guest: { name: "Guest", email: "guest@example.com" }
};
(5) Pick<T, K>
- 定义:从类型
T
中选择一组属性K
,构造一个新的类型 - 语法:
type Pick<T, K extends keyof T> = { [P in K]: T[P] };
- 示例:
interface User {
id: number;
name: string;
email: string;
}
type UserPreview = Pick<User, "id" | "name">;
const user: UserPreview = {
id: 1,
name: "John Doe"
};
(6) Omit<T, K>
- 定义:从类型
T
中排除所有属于类型U
的属性 - 语法:
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;
- 示例:
interface User {
id: number;
name: string;
email: string;
age: number;
}
type UserWithoutEmailAndAge = Omit<User, 'email' | 'age'>;
const user: UserWithoutEmailAndAge = {
id: 1,
name: 'John Doe',
// email: 'john.doe@example.com' // 属性不存在
// age: 30 // 属性不存在
};
(7) Exclude<T, U>
- 定义:从类型
T
中排除所有属于类型U
的属性 - 语法:
type Exclude<T, U> = T extends U ? never : T;
- 示例:
type T = "a" | "b" | "c";
type U = "a" | "c";
type Excluded = Exclude<T, U>; // "b"
(8) Extract<T, U>
- 定义:从类型
T
中提取所有属于类型U
的属性 - 语法:
type Extract<T, U> = T extends U ? T : never;
- 示例:
type T = "a" | "b" | "c";
type U = "a" | "c";
type Extracted = Extract<T, U>; // "a" | "c"
(9) Extract<T, U>
- 定义:从类型
T
中排除null
和undefined
- 语法:
type NonNullable<T> = T extends null | undefined ? never : T;
- 示例:
type T = string | number | null | undefined;
type NonNullableT = NonNullable<T>; // string | number
(10) ReturnType<T>
-
定义:获取函数类型
T
的返回类型 -
语法:
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;
-
示例:
function getUser() {
return { id: 1, name: "John Doe" };
}
type User = ReturnType<typeof getUser>; // { id: number, name: string }
(11) InstanceType<T>
-
定义:获取构造函数类型
T
的实例类型 -
语法:
type InstanceType<T extends new (...args: any) => any> = T extends new (...args: any) => infer R ? R : any;
-
示例:
class User {
constructor(public id: number, public name: string) {}
}
type UserInstance = InstanceType<typeof User>; // User
八、类型兼容性
1. 接口的兼容性
接口的兼容性是基于对象的形状进行的。如果一个对象包含了目标接口所需的所有属性,那么它们是兼容的。
interface Point {
x: number;
y: number;
}
let point: Point;
let point3D = { x: 1, y: 2, z: 3 };
point = point3D; // 兼容,point3D包含point所需的所有属性
// point3D = point; // 不兼容,point没有z属性
2. 类的兼容性
如果类包含私有或受保护的成员,那么只有源类和目标类是同一个类时,它们才互相兼容。
class Animal {
feet: number;
constructor(name: string, numFeet: number) { }
}
class Size {
feet: number;
constructor(numFeet: number) { }
}
let a: Animal;
let s: Size;
a = s; // 兼容,形状一致
s = a; // 兼容,形状一致
3. 函数的兼容性
目标函数的参数必须是源函数参数的超集,目标函数返回值类型必须是源函数的子集。
let x = (a: number) => 0;
let y = (b: number, s: string) => 0;
y = x; // 兼容,因为y可以忽略s参数
// x = y; // 不兼容,因为x缺少s参数
let fn1 = () => ({ name: 'Alice' });
let fn2 = () => ({ name: 'Alice', location: 'Seattle' });
fn1 = fn2; // 兼容,fn2的返回值包含fn1的所有属性
// fn2 = fn1; // 不兼容,fn1返回值缺少location
4. 枚举不兼容
不同枚举类型之间是不兼容的,即使它们的值相同。
5. 泛型的兼容性
泛型类型在兼容性判断时会考虑实际的类型参数。
interface Empty<T> {
value: T;
}
let x1: Empty<number>;
let x2: Empty<string>;
// x1 = x2; // 不兼容,参数类型不同
let identity = <T>(x: T): T => x;
let reverse = <U>(y: U): U => y;
identity = reverse; // 兼容,结构相同
九、类型声明文件
类型声明文件.d.ts
,用于声明 JavaScript 代码中的变量、函数、类等的类型。让 TypeScript 编译器理解这些 JavaScript 代码中的结构和接口,以提供类型检查和自动补全功能。
declare const myVariable: string;
declare function myFunction(arg: number): boolean;
declare class MyClass {
constructor(name: string);
greet(): string;
}
declare interface MyInterface {
property: string;
method(arg: number): void;
}
// 模块声明
declare module 'my-module' {
export function myFunction(arg: number): string;
}
// import { myFunction } from "my-module";
对于流行的第三方 JavaScript 库,通常会有现成的类型声明文件。这些声明文件可以通过 DefinitelyTyped 项目获取,并通过 npm 安装。例如:npm install @types/lodash