01-TypeScript类型系统

📋 学习目标

  • 掌握TypeScript基本类型

  • 理解类型推断和类型注解

  • 学习高级类型特性

  • 掌握类型守卫和类型断言

🔢 基本类型

原始类型

// 数字
let age: number = 30;
let price: number = 99.99;
let hex: number = 0xf00d;

// 字符串
let name: string = 'John';
let message: string = `Hello, ${name}`;

// 布尔
let isDone: boolean = false;

// null和undefined
let u: undefined = undefined;
let n: null = null;

// 可选链
type User = {
    name: string;
    age?: number;  // 可选属性
};

数组和元组

// 数组
let numbers: number[] = [1, 2, 3];
let strings: Array<string> = ['a', 'b', 'c'];

// 元组
let tuple: [string, number] = ['hello', 10];
let rgb: [number, number, number] = [255, 0, 0];

// 只读数组
let readonly: ReadonlyArray<number> = [1, 2, 3];
// readonly.push(4); // 报错

// 只读元组
let point: readonly [number, number] = [10, 20];

any、unknown、never

// any:任意类型(失去类型检查)
let anything: any = 'hello';
anything = 123;
anything.foo(); // 不报错,但可能运行时出错

// unknown:类型安全的any
let value: unknown = 'hello';
// value.toUpperCase(); // 报错
if (typeof value === 'string') {
    value.toUpperCase(); // 正确
}

// never:永不存在的值
function error(message: string): never {
    throw new Error(message);
}

function infiniteLoop(): never {
    while (true) {}
}

// void:没有返回值
function log(message: string): void {
    console.log(message);
}

枚举

// 数字枚举
enum Direction {
    Up,      // 0
    Down,    // 1
    Left,    // 2
    Right    // 3
}

let dir: Direction = Direction.Up;

// 字符串枚举
enum Color {
    Red = 'RED',
    Green = 'GREEN',
    Blue = 'BLUE'
}

// 常量枚举(编译时内联)
const enum Size {
    Small,
    Medium,
    Large
}

let size: Size = Size.Medium;

📦 接口与类型别名

接口定义

// 基本接口
interface User {
    name: string;
    age: number;
    email?: string;  // 可选
    readonly id: number;  // 只读
}

const user: User = {
    id: 1,
    name: 'John',
    age: 30
};

// user.id = 2; // 报错:只读属性

// 索引签名
interface StringMap {
    [key: string]: string;
}

const map: StringMap = {
    name: 'John',
    city: 'Beijing'
};

// 函数类型
interface SearchFunc {
    (source: string, sub: string): boolean;
}

const search: SearchFunc = (src, sub) => {
    return src.includes(sub);
};

// 类接口
interface ClockInterface {
    currentTime: Date;
    setTime(d: Date): void;
}

class Clock implements ClockInterface {
    currentTime: Date = new Date();
    setTime(d: Date) {
        this.currentTime = d;
    }
}

类型别名

// 基本类型别名
type ID = string | number;
type Point = {
    x: number;
    y: number;
};

// 联合类型
type Status = 'pending' | 'success' | 'error';

// 交叉类型
type Name = {name: string};
type Age = {age: number};
type Person = Name & Age;

const person: Person = {
    name: 'John',
    age: 30
};

// 函数类型
type AddFn = (a: number, b: number) => number;

const add: AddFn = (a, b) => a + b;

Interface vs Type

// interface可以扩展
interface Animal {
    name: string;
}

interface Dog extends Animal {
    bark(): void;
}

// interface可以合并
interface Window {
    title: string;
}

interface Window {
    width: number;
}

// type可以使用联合类型
type StringOrNumber = string | number;

// type可以使用映射类型
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

🎯 泛型

泛型函数

// 基本泛型
function identity<T>(arg: T): T {
    return arg;
}

let output1 = identity<string>('hello');
let output2 = identity(123); // 类型推断

// 泛型数组
function getFirst<T>(arr: T[]): T | undefined {
    return arr[0];
}

const first = getFirst([1, 2, 3]); // number | undefined

// 多个类型参数
function pair<T, U>(first: T, second: U): [T, U] {
    return [first, second];
}

const p = pair('hello', 123); // [string, number]

泛型接口

interface GenericIdentity<T> {
    (arg: T): T;
}

function identity<T>(arg: T): T {
    return arg;
}

let myIdentity: GenericIdentity<number> = identity;

// 泛型类
class GenericNumber<T> {
    zeroValue: T;
    add: (x: T, y: T) => T;
    
    constructor(zero: T, addFn: (x: T, y: T) => T) {
        this.zeroValue = zero;
        this.add = addFn;
    }
}

const myNumber = new GenericNumber<number>(0, (x, y) => x + y);

泛型约束

// 约束泛型
interface Lengthwise {
    length: number;
}

function logLength<T extends Lengthwise>(arg: T): T {
    console.log(arg.length);
    return arg;
}

logLength('hello');  // 正确
logLength([1, 2, 3]); // 正确
// logLength(123); // 报错:number没有length属性

// keyof约束
function getProperty<T, K extends keyof T>(obj: T, key: K): T[K] {
    return obj[key];
}

const obj = {a: 1, b: 2, c: 3};
getProperty(obj, 'a'); // 正确
// getProperty(obj, 'd'); // 报错

🔍 类型守卫

typeof

function padLeft(value: string, padding: string | number) {
    if (typeof padding === 'number') {
        return ' '.repeat(padding) + value;
    }
    return padding + value;
}

instanceof

class Bird {
    fly() {
        console.log('flying');
    }
}

class Fish {
    swim() {
        console.log('swimming');
    }
}

function move(pet: Bird | Fish) {
    if (pet instanceof Bird) {
        pet.fly();
    } else {
        pet.swim();
    }
}

自定义类型守卫

interface Cat {
    meow(): void;
}

interface Dog {
    bark(): void;
}

function isCat(pet: Cat | Dog): pet is Cat {
    return (pet as Cat).meow !== undefined;
}

function makeSound(pet: Cat | Dog) {
    if (isCat(pet)) {
        pet.meow();
    } else {
        pet.bark();
    }
}

🎨 高级类型

联合类型

type StringOrNumber = string | number;

function format(value: StringOrNumber): string {
    if (typeof value === 'string') {
        return value.toUpperCase();
    }
    return value.toString();
}

交叉类型

interface Colorful {
    color: string;
}

interface Circle {
    radius: number;
}

type ColorfulCircle = Colorful & Circle;

const cc: ColorfulCircle = {
    color: 'red',
    radius: 10
};

条件类型

type IsString<T> = T extends string ? 'yes' : 'no';

type A = IsString<string>; // 'yes'
type B = IsString<number>; // 'no'

// 实际应用
type Flatten<T> = T extends Array<infer U> ? U : T;

type Str = Flatten<string[]>; // string
type Num = Flatten<number>;   // number

映射类型

// 基本映射
type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

type Optional<T> = {
    [P in keyof T]?: T[P];
};

// 使用
interface Todo {
    title: string;
    completed: boolean;
}

type ReadonlyTodo = Readonly<Todo>;
type PartialTodo = Optional<Todo>;

工具类型

// Partial:所有属性可选
type PartialUser = Partial<User>;

// Required:所有属性必填
type RequiredUser = Required<User>;

// Readonly:所有属性只读
type ReadonlyUser = Readonly<User>;

// Pick:选择部分属性
type UserPreview = Pick<User, 'name' | 'email'>;

// Omit:排除部分属性
type UserWithoutId = Omit<User, 'id'>;

// Record:构造对象类型
type PageInfo = Record<'home' | 'about' | 'contact', {title: string}>;

// Exclude:排除联合类型
type T0 = Exclude<'a' | 'b' | 'c', 'a'>; // 'b' | 'c'

// Extract:提取联合类型
type T1 = Extract<'a' | 'b' | 'c', 'a' | 'f'>; // 'a'

// NonNullable:排除null和undefined
type T2 = NonNullable<string | null | undefined>; // string

// ReturnType:获取函数返回类型
type T3 = ReturnType<() => string>; // string

💡 实践技巧

类型断言

// as语法
const input = document.getElementById('input') as HTMLInputElement;
input.value = 'hello';

// 双重断言
const value = 'hello' as unknown as number; // 不推荐

// 非空断言
function getName(user?: User) {
    return user!.name; // 断言user不为null/undefined
}

类型收窄

function process(value: string | null) {
    // 类型守卫
    if (value !== null) {
        console.log(value.toUpperCase());
    }
    
    // 短路运算
    value && console.log(value.length);
    
    // 空值合并
    const name = value ?? 'Guest';
}

📚 实践练习

练习1:类型定义

为以下场景定义类型:

  • API响应数据

  • 表单配置

  • 路由配置

练习2:泛型工具

实现工具类型:

  • DeepPartial:深度可选

  • DeepReadonly:深度只读

  • ValueOf:获取对象值类型

练习3:类型守卫

实现类型守卫函数:

  • isArray

  • isObject

  • isPromise

📚 参考资料