入门
Hello World 与编译
TypeScript 文件使用 .ts 扩展名。tsc 编译器将 TS 转译为 JS,在运行时擦除所有类型注解。使用 --strict 获得最大类型安全。ts-node 或 bun 可直接运行 .ts 文件而无需单独编译步骤。
// hello.ts
const message: string = "Hello, TypeScript!";
console.log(message);
// Compile to JavaScript (type annotations erased):
// tsc hello.ts -> hello.js
// tsc --strict hello.ts // enable all strict checks
// tsc --watch hello.ts // recompile on change
// Run directly with ts-node or bun:
// ts-node hello.tstsconfig.json
tsconfig.json 配置 TypeScript 编译器。'strict: true' 启用 noImplicitAny、strictNullChecks、strictFunctionTypes 等。'target' 控制输出 JS 版本。'esModuleInterop' 启用从 CommonJS 模块(如 Node 内置模块)的默认导入。
{
"compilerOptions": {
"target": "ES2020", // JS version to emit
"module": "ESNext", // module system
"strict": true, // enable all strict checks
"outDir": "./dist", // output directory
"rootDir": "./src", // source root
"esModuleInterop": true, // allow default imports from CJS
"skipLibCheck": true, // skip .d.ts type checking
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}类型注解与推断
类型注解显式指定变量的类型。TypeScript 也可从值推断类型。函数签名和公共 API 使用显式注解;局部变量依赖推断。避免 'any' —— 它完全退出类型检查。
// Explicit type annotations
let count: number = 0;
const name: string = "Alice";
let isDone: boolean = true;
let ids: number[] = [1, 2, 3];
let tuple: [string, number] = ["age", 30];
// Type inference (let TS figure it out)
let age = 30; // inferred as number
let items = [1, 2, 3]; // inferred as number[]
let mixed = [1, "a"]; // inferred as (string | number)[]
// any disables type checking (avoid!)
let anything: any = 42;
anything = "string"; // no error严格模式检查
严格模式启用关键检查:strictNullChecks(null/undefined 不可赋值给其他类型)、noImplicitAny(参数必须有类型)、strictPropertyInitialization(类字段必须初始化)。当确定字段稍后会被设置时使用 '!'(确定赋值)。
// strictNullChecks: null/undefined not assignable to other types
let n: string = null; // Error in strict mode
// noImplicitAny: must annotate parameters
function greet(name) { } // Error: implicit any
// strictPropertyInitialization
class User {
name: string; // Error: not initialized
constructor() {}
}
// Fix: initialize or use definite assignment
class Fixed {
name!: string; // definite assignment assertion
age: number = 0;
}声明文件 (.d.ts)
声明文件 (.d.ts) 为没有 TypeScript 定义的 JavaScript 库提供类型。'declare' 告诉编译器变量/函数在运行时存在。使用 DefinitelyTyped 的 @types 包获取流行库(如 @types/node、@types/react)。
// types.d.ts - type declarations only, no implementation
declare module "my-lib" {
export function greet(name: string): string;
export const version: string;
}
// global.d.ts - extend global scope
declare global {
interface Window {
myApp: { version: string };
}
}
// Usage in .ts files:
// import { greet } from "my-lib"; // now typed
// Install @types packages: npm i -D @types/node @types/react基本类型
基本类型与特殊类型
TypeScript 基本类型:string、number、boolean、bigint、symbol。'void' 表示函数不返回值。'never' 表示永不出现的值 —— 抛出或永远运行的函数。在 switch 语句中使用 'never' 进行穷尽检查。
let str: string = "hello";
let num: number = 42;
let bool: boolean = true;
let big: bigint = 100n;
let sym: symbol = Symbol("id");
// void: function returns nothing
function log(msg: string): void { console.log(msg); }
// never: function never returns
function fail(msg: string): never { throw new Error(msg); }
function infinite(): never { while (true) {} }数组与元组
数组使用 T[] 或 Array<T> 语法。ReadonlyArray 防止修改。元组是固定长度数组,每个位置有特定类型 —— 适用于键值对或类 CSV 数据。标记元组通过命名位置提高可读性。
// Two syntaxes for arrays
let nums: number[] = [1, 2, 3];
let strs: Array<string> = ["a", "b"];
// ReadonlyArray (immutable)
const ro: ReadonlyArray<number> = [1, 2, 3];
// ro.push(4); // Error: not mutable
// Tuple (fixed length, known types)
let tuple: [string, number] = ["Alice", 30];
let name = tuple[0]; // string
let age = tuple[1]; // number
// Labeled tuple elements (TS 4.0+)
let entry: [name: string, age: number] = ["Bob", 25];枚举
枚举定义一组命名常量。推荐使用字符串枚举(输出中值可读)。数字枚举支持反向映射。'const enum' 在编译时被擦除(内联)以实现 零运行时成本。简单情况优先使用联合类型。
// Numeric enum (default starts at 0)
enum Direction { Up, Down, Left, Right }
let d: Direction = Direction.Up; // 0
// String enum (recommended for readability)
enum Status {
Pending = "PENDING",
Success = "SUCCESS",
Failed = "FAILED",
}
// Reverse mapping (numeric only)
console.log(Direction[0]); // "Up"
// Const enum (inlined, no runtime object)
const enum Color { Red, Green, Blue }
let c = Color.Red; // compiles to: let c = 0;any vs unknown vs never
'any' 禁用所有类型检查 —— 避免使用。'unknown' 是类型安全的替代:使用前必须收窄(通过 typeof、instanceof)。'never' 表示永不出现的值,用于 switch 语句中的穷尽检查以在编译时捕获遗漏情况。
// any: opt out of type checking (dangerous!)
let a: any = 42;
a = "string"; // OK
a.toUpperCase(); // OK (no check, may fail at runtime)
// unknown: type-safe alternative to any
let u: unknown = 42;
// u.toUpperCase(); // Error: unknown type
if (typeof u === "string") {
u.toUpperCase(); // OK after narrowing
}
// never: impossible value (exhaustiveness check)
type Shape = "circle" | "square";
function area(s: Shape) {
switch (s) {
case "circle": return Math.PI;
case "square": return 1;
default:
const _exhaustive: never = s; // Error if case missing
}
}类型断言
类型断言告诉编译器'相信我,我知道类型'。使用 'as' 语法。非空断言 (!) 告诉 TS 值不是 null/undefined。'as const' 使所有属性成为只读字面量 —— 适用于配置对象和 Redux action 类型。断言不改变运行时行为。
// as syntax (preferred, works in .tsx)
let val: unknown = "hello";
let len: number = (val as string).length;
// Non-null assertion (!)
let el = document.querySelector("#app")!;
el.innerHTML = "Hi"; // el is HTMLElement, not null
// Double assertion (for unsafe casts)
let value = "42" as unknown as number;
// const assertion (literal types)
const req = { method: "GET", url: "/api" } as const;
// req.method has type "GET" (not string)
// req is readonly字面量与联合类型
字面量类型将值限制为特定字符串、数字或布尔值。与联合结合,创建精确类型如方向或 HTTP 方法。模板字面量类型 (TS 4.1+) 从其他类型构建字符串类型 —— 强大用于生成类型安全的键和事件名。
// String literal types
let direction: "left" | "right" | "up" | "down";
direction = "left"; // OK
// direction = "sideways"; // Error
// Numeric literal types
let dice: 1 | 2 | 3 | 4 | 5 | 6;
dice = 4; // OK
// Boolean literal
let flag: true = true;
// Template literal types (TS 4.1+)
type Color = "red" | "blue";
type Size = "small" | "large";
type Variant = `${Size}-${Color}`;
// "small-red" | "small-blue" | "large-red" | "large-blue"接口与对象
接口基础
接口描述对象的形状。'?' 标记可选属性(可能为 undefined)。'readonly' 防止初始化后重新赋值。接口仅在编译时存在 —— 在输出 JavaScript 中被擦除。用于为对象和类 定义契约。
interface User {
id: number;
name: string;
email?: string; // optional property
readonly createdAt: Date; // immutable
}
const user: User = {
id: 1,
name: "Alice",
createdAt: new Date(),
};
// user.createdAt = new Date(); // Error: readonly
// user.email; // string | undefined索引签名
索引签名允许具有给定类型的任意键的对象。所有属性值必须可赋值给索引类型。适用于字典、缓存和动态数据。与已知属性结合用于带额外选项的类型化配置。
// Index signature: arbitrary string keys
interface StringMap {
[key: string]: string;
}
const dict: StringMap = {
name: "Alice",
city: "NYC",
// count: 42, // Error: value must be string
};
// Mixed: known + index signature
interface Config {
name: string;
[key: string]: string | number;
}
// Readonly index signature
interface ReadonlyMap {
readonly [key: string]: number;
}扩展接口
接口可扩展一个或多个其他接口,组合其成员。这实现了组合和代码复用。与类不同,接口支持多重继承。实现接口时,类必须提供所有必需成员。
interface Animal {
name: string;
eat(): void;
}
interface Pet extends Animal {
owner: string;
play(): void;
}
interface Swimmer {
swim(): void;
}
// Multiple inheritance
interface Duck extends Pet, Swimmer {
quack(): void;
}
const duck: Duck = {
name: "Donald",
owner: "Walt",
eat() {},
play() {},
swim() {},
quack() {},
};接口中的函数类型
接口可描述函数签名,实现类型安全回调。混合接口(可调用 + 属性)用于 jQuery 风格的函数,这些函数也有方法。此模式在返回带附加辅助函数的函数的库中常见。
interface SearchFn {
(source: string, keyword: string): boolean;
}
const contains: SearchFn = (src, kw) => src.includes(kw);
console.log(contains("hello world", "world")); // true
// Interface with mixed members (hybrid)
interface Counter {
(start: number): void; // callable
count: number; // property
reset(): void; // method
}
// Function with properties (jQuery-style)
const counter: any = (n: number) => { counter.count = n; };
counter.count = 0;
counter.reset = () => { counter.count = 0; };接口 vs 类型别名
接口支持声明合并(同名接口合并)、更好的错误消息,优先用于对象/类形状。类型别名更灵活(可表示联合、基本类型、元组)但不能合并。可扩展 API 使用接口,联合和计算类型使用类型别名。
// Interface: extendable, better error messages
interface Window { title: string; }
interface Window { size: number; } // declaration merging
const w: Window = { title: "App", size: 800 };
// Type alias: more flexible (unions, primitives, etc.)
type ID = string | number;
type Callback<T> = (value: T) => void;
// Both can describe object shapes
interface UserI { name: string; }
type UserT = { name: string; };
// Use interface for objects/classes, type for unions/aliases可选链与空值合并
可选链 (?.) 安 全访问嵌套属性 —— 如果任何链接为 null/undefined 则返回 undefined 而非抛出。空值合并 (??) 仅对 null/undefined(非 0 或 '')提供默认值。这些运算符大幅减少冗长的空检查代码。
interface User {
profile?: {
address?: {
city?: string;
};
};
}
const user: User = {};
// Optional chaining (?.) - safe property access
const city = user.profile?.address?.city; // string | undefined
// Nullish coalescing (??) - default value
const name = user.profile?.address?.city ?? "Unknown";
// Non-null assertion (!) - you're sure it's not null
// const c = user.profile!.address!.city!; // risky
// Optional method call
const result = user.profile?.address?.city?.toUpperCase();类型别名与联合
类型别名
类型别名为任何类型创建命名引用,包括联合、交叉、基本类型和泛型。与接口不同,别名不能合并或扩展,但更灵活。联合、元组和工具类型使用别名;对象形状使用接口。
// Basic alias
type ID = string | number;
type Point = { x: number; y: number };
// Generic alias
type Container<T> = { value: T };
// Function type alias
type Handler<T> = (event: T) => void;
// Usage
const id: ID = 42;
const p: Point = { x: 1, y: 2 };
const box: Container<string> = { value: "hi" };
const onClick: Handler<string> = (e) => console.log(e);联合类型
联合类型 (A | B) 允许值是多种类型之一。TypeScript 在条件块内使用 typeof、instanceof 或 in 检查收窄类型。注意:(string | number)[] 与 string[] | number[] 不同 —— 前者是混合数组,后者是全字符串或全数字。
// Union: value can be one of several types
type ID = string | number;
function display(id: ID) {
if (typeof id === "string") {
console.log(id.toUpperCase()); // narrowed to string
} else {
console.log(id.toFixed(2)); // narrowed to number
}
}
display("abc"); // ABC
display(42); // 42.00
// Union of arrays vs array of unions
type Mixed = (string | number)[];
type Either = string[] | number[];交叉类型
交叉类型 (A & B) 组合多种类型的所有成员 —— 结果必须满足每个类型。适用于 mixin、组合和合并工具类型。与联合 (OR) 不同,交叉是 AND:值必须具有所有类型的所有属性。
// Intersection: combine multiple types into one
interface BusinessPartner {
name: string;
credit: number;
}
interface Identity {
id: number;
email: string;
}
type Employee = BusinessPartner & Identity;
const emp: Employee = {
name: "Alice",
credit: 1000,
id: 1,
email: "[email protected]",
};
// All properties required
// const bad: Employee = { name: "Bob" }; // Error: missing props可空类型
严格模式下,null 和 undefined 不可赋值给其他类型 —— 必须用联合显式包含 (string | null)。可选参数 (param?) 隐式为 T | undefined。使用 ?? 进行安全默认值,用 ! 断言非空(谨慎使用)。
// In strict mode, null/undefined are separate types
let name: string | null = null;
name = "Alice"; // OK
// Optional parameters are implicitly | undefined
function greet(name?: string) {
// name is string | undefined
console.log(name ?? "Guest");
}
// Return type can be null
function find(id: number): string | null {
return id === 1 ? "Alice" : null;
}
// Non-null assertion
const result = find(1)!.toUpperCase(); // "ALICE"Keyof 与 Typeof 运算符
'keyof T' 将类型 T 的键提取为字符串字面量联合。'typeof x' 提取值的类型(适用于从对象推断)。'keyof typeof obj' 结合两者获取现有对象的键 —— 在 Redux action 类型和类型安全属性访问器中常见。
interface User {
id: number;
name: string;
email: string;
}
// keyof: extract keys as a union
type UserKey = keyof User; // "id" | "name" | "email"
function getProp(obj: User, key: keyof User) {
return obj[key];
}
// typeof: extract type from a value
const config = { port: 3000, host: "localhost" };
type Config = typeof config; // { port: number; host: string }
// keyof typeof: keys of an object
type ConfigKey = keyof typeof config; // "port" | "host"映射类型
映射类型遍历键以转换类型。内置工具如 Readonly、Partial 和 Pick 是映射类型。使用 + 和 - 修饰符添加/移除 readonly 或可选。键重映射 (TS 4.1+) 使用模板字面量类型重命名键 —— 强大用于生成 getter/setter 类型。
// Map over keys to create a new type
type Readonly<T> = {
readonly [K in keyof T]: T[K];
};
type Optional<T> = {
[K in keyof T]?: T[K];
};
interface User { id: number; name: string; }
type ReadonlyUser = Readonly<User>; // all readonly
type OptionalUser = Optional<User>; // all optional
// Modifiers: +add, -remove
type Mutable<T> = { -readonly [K in keyof T]: T[K]; };
type Required<T> = { [K in keyof T]-?: T[K]; };
// Key remapping (TS 4.1+)
type Getters<T> = {
[K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};函数
函数类型与签名
TypeScript 为函数参数和返回值添加类型注解。返回类型通常可推断,但公共 API 推荐显式注解。默认参数使参数可选并带后备值。函数不返回任何内容 时使用 void。
// Named function with types
function add(a: number, b: number): number {
return a + b;
}
// Arrow function with types
const multiply = (a: number, b: number): number => a * b;
// Function type alias
type MathOp = (a: number, b: number) => number;
const divide: MathOp = (a, b) => a / b;
// Void return (no return value)
function log(msg: string): void { console.log(msg); }
// Optional and default parameters
function greet(name: string, greeting: string = "Hi"): string {
return `${greeting}, ${name}!`;
}
greet("Alice"); // "Hi, Alice!"
greet("Bob", "Hello"); // "Hello, Bob!"剩余参数与元组
剩余参数 (...args) 将多个参数收集到数组中。TypeScript 将它们类型化为 T[] 或元组用于固定长度可变参数函数。展开运算符 (...) 执行相反操作 —— 将数组展开为单独参数。元组剩余类型实现精确的可变参数签名。
// Rest parameters (variadic)
function sum(...nums: number[]): number {
return nums.reduce((a, b) => a + b, 0);
}
console.log(sum(1, 2, 3, 4)); // 10
// Tuple rest (fixed prefix + variadic tail)
function pair(name: string, ...scores: number[]): void {
console.log(name, scores);
}
// Spread call
const nums = [1, 2, 3];
console.log(sum(...nums));
// Typed rest as tuple
function f(...args: [string, number, boolean]): void {
const [s, n, b] = args;
}函数重载
函数重载为同一函数提供多个类型签名,基于输入实现精确返回类型。实现签名对调用者隐藏。重载自上而下解析 —— 将更具体的签名放在前面。在 jQuery 和 lodash 等库中常见。
// Overload signatures (what callers see)
function parse(input: string): string[];
function parse(input: number): number[];
// Implementation signature (not visible to callers)
function parse(input: string | number): string[] | number[] {
if (typeof input === "string") {
return input.split(",");
}
return [input, input * 2];
}
const strs = parse("a,b,c"); // string[]
const nums = parse(42); // number[]
// Overloads with different param counts
function makeDate(timestamp: number): Date;
function makeDate(y: number, m: number, d: number): Date;
function makeDate(yOrTs: number, m?: number, d?: number): Date {
return m === undefined
? new Date(yOrTs)
: new Date(yOrTs, m - 1, d);
}this 类型
TypeScript 允许将 'this' 类型声明为第一个参数。这确保函数以正确上下文调用 —— 适用于作为回调传递的方法。箭头函数词法捕获 'this',避免 .bind() 的需要。使用 'noImplicitThis' 捕获未类型化的 'this' 错误。
interface Card {
suit: string;
rank: string;
isFaceUp(): boolean;
}
// Explicit 'this' parameter
function format(this: Card): string {
return `${this.rank} of ${this.suit}`;
}
const card: Card = {
suit: "Hearts",
rank: "A",
isFaceUp() { return true; },
format,
};
// 'this' in callbacks with bind
class Handler {
private count = 0;
increment = () => { this.count++; }; // arrow binds this
}回调与高阶函数
TypeScript 完全类型化高阶函数(接受或返回函数的函数)。使用泛型类型参数 (T, U) 保留输入和输出之间的类型关系。回调类型通常定义为类型别名以便复用。柯里化(返回函数)完全类型安全。
// Function as parameter
type Callback<T> = (value: T, index: number) => void;
function forEach<T>(arr: T[], cb: Callback<T>): void {
for (let i = 0; i < arr.length; i++) {
cb(arr[i], i);
}
}
forEach(["a", "b"], (v, i) => console.log(i, v));
// Function returning function (curry)
function add(a: number): (b: number) => number {
return (b) => a + b;
}
const add5 = add(5);
console.log(add5(3)); // 8
// Generic map
function map<T, U>(arr: T[], fn: (x: T) => U): U[] {
return arr.map(fn);
}参数解构
TypeScript 支持函数参数中的解构 —— 内联注解解构形状或通过接口。提取到接口提高可读性和复用。数组/元组解构也可用。此模式在 React 组件 props 和 API 处理程序中常见。
// Destructured parameters with types
function createUser({ name, age, email }: {
name: string;
age: number;
email?: string;
}): void {
console.log(name, age, email);
}
createUser({ name: "Alice", age: 30 });
// Extract to interface for reuse
interface UserOpts {
name: string;
age: number;
email?: string;
}
function updateUser({ name, age }: UserOpts): void {}
// Array destructuring in params
function swap([a, b]: [number, number]): [number, number] {
return [b, a];
}类与 OOP
类与构造函数
TypeScript 类支持参数属性 —— 用访问修饰符 (public/private/protected/readonly) 前缀构造函数参数会自动创建并赋值字段。此简写减少样板代码。方法可在返回值上有类型注解。字段默认为 public。
class Person {
// Parameter properties (shorthand)
constructor(
public name: string, // auto-creates this.name
private age: number, // private field
readonly id: number, // immutable
) {}
greet(): string {
return `Hi, I'm ${this.name}`;
}
}
const p = new Person("Alice", 30, 1);
console.log(p.name); // "Alice" (public)
// p.age; // Error: private
// p.id = 2; // Error: readonly访问修饰符
访问修饰符:public(默认,到处)、private(仅类)、protected(类 + 子类)、readonly(不可变)。TypeScript 的 'private' 仅编译时;ES '#' 私有字段在运行时真正私有。private 用于实现细节,protected 用于扩展点。
class BankAccount {
public owner: string; // accessible everywhere
private balance: number; // class only
protected rate: number; // class + subclasses
readonly id: string; // immutable after init
#secret: string; // ES private (runtime)
constructor(owner: string) {
this.owner = owner;
this.balance = 0;
this.rate = 0.05;
this.id = crypto.randomUUID();
this.#secret = "hidden";
}
deposit(amount: number): void {
this.balance += amount;
}
}继承与抽象类
抽象类不能直接实例化 —— 它们为子类定义基类。抽象方法在基类中没有实现;子类必须实现。使用 'extends' 继承,'super()' 调用父构造函数。抽象类实现多态 —— 代码可适用于任何 Shape 子类。
abstract class Shape {
constructor(public color: string) {}
abstract area(): number; // must be implemented
describe(): string {
return `${this.color} shape, area ${this.area()}`;
}
}
class Circle extends Shape {
constructor(color: string, private r: number) {
super(color);
}
area(): number { return Math.PI * this.r ** 2; }
}
class Square extends Shape {
constructor(color: string, private side: number) {
super(color);
}
area(): number { return this.side ** 2; }
}
const c = new Circle("red", 5);
console.log(c.describe()); // "red shape, area 78.54..."
// new Shape("blue"); // Error: cannot instantiate abstract接口与实现
一个类可实现多个接口(用逗号分隔)。类必须提供所有接口成员。与 extends(单继承)不同,implements 支持多个契约。这是 TypeScript 实现类似多重继承行为的方式。用接口定义契约,用类实现它们。
interface Printable {
toString(): string;
}
interface Comparable<T> {
compareTo(other: T): number;
}
class Money implements Printable, Comparable<Money> {
constructor(private amount: number) {}
toString(): string {
return `$${this.amount.toFixed(2)}`;
}
compareTo(other: Money): number {
return this.amount - other.amount;
}
}
const a = new Money(10);
const b = new Money(20);
console.log(a.toString()); // "$10.00"
console.log(a.compareTo(b)); // -10Getter 与 Setter
Getter 和 setter 拦截属性访问用于验证、计算或副作用。使用私有后备字段(约定:下划线前缀)。Getter 实现计算属性(如从摄氏度得到华氏度)。Setter 实现验证。像常规属性一样访问 —— 无括号。
class Temperature {
private _celsius: number = 0;
get celsius(): number {
return this._celsius;
}
set celsius(value: number) {
if (value < -273.15) throw new Error("Below absolute zero");
this._celsius = value;
}
get fahrenheit(): number {
return this._celsius * 9 / 5 + 32;
}
set fahrenheit(value: number) {
this.celsius = (value - 32) * 5 / 9;
}
}
const temp = new Temperature();
temp.celsius = 25;
console.log(temp.fahrenheit); // 77
// temp.celsius = -300; // throws静态成员与单例
静态成员属于类而非实例 —— 通过 ClassName.member 访问。static 用于常量、工具函数和工厂方法。私有构造函数 + 静态 getInstance() 实现单例模式。'as const' 使静态数组成为带字面量类型的只读。
class Logger {
static instance: Logger;
private logs: string[] = [];
private constructor() {} // prevent direct new
static getInstance(): Logger {
if (!Logger.instance) {
Logger.instance = new Logger();
}
return Logger.instance;
}
static readonly LEVELS = ["INFO", "WARN", "ERROR"] as const;
log(msg: string): void {
this.logs.push(msg);
}
}
const logger = Logger.getInstance();
console.log(Logger.LEVELS); // ["INFO", "WARN", "ERROR"]
// new Logger(); // Error: private constructor