普通视图

发现新文章,点击刷新页面。
昨天 — 2025年7月1日首页

TypeScript入门(七)高级类型:解锁TypeScript的"终极形态"

2025年7月1日 17:28

第7章 高级类型:解锁TypeScript的"终极形态"

想象你正在探索一座神秘的类型魔法学院——高级类型(Advanced Types) 就是这里最强大的魔法咒语。如果说基础类型是编程世界的砖瓦,那么高级类型就是建筑大师的蓝图工具。这一章,我们将学会如何运用这些类型魔法,让TypeScript从优秀走向卓越!

7.1 类型别名——给复杂类型起个"好记的名字"

类型别名(Type Aliases)就像给你的类型定制一张专属名片——复杂类型从此有了简洁易记的称呼。

🏷️ 基础类型别名:简化复杂类型

// 1. 基础类型别名 - 联合类型的简化
type UserID = string | number;
type Coordinate = [number, number];

// 使用示例
const userId1: UserID = "U1001";
const userId2: UserID = 12345;
const position: Coordinate = [100, 200];

console.log(userId1); // "U1001"
console.log(userId2); // 12345
console.log(position); // [100, 200]

// 2. 对象类型别名 - 复杂结构的命名
type UserProfile = {
    id: UserID;
    name: string;
    email?: string;
    createdAt: Date;
};

// 使用示例
const user: UserProfile = {
    id: "U1001",
    name: "技术宅",
    email: "tech@example.com",
    createdAt: new Date()
};

console.log(user.name); // "技术宅"
console.log(user.id); // "U1001"
console.log(typeof user.createdAt); // "object"

// 3. 函数类型别名 - 函数签名的命名
type StringProcessor = (input: string) => string;
type NumberValidator = (value: number) => boolean;

// 实现函数
const toUpper: StringProcessor = (str) => {
    const result = str.toUpperCase();
    console.log(`转换:"${str}" -> "${result}"`);
    return result;
};

const isPositive: NumberValidator = (num) => {
    const result = num > 0;
    console.log(`验证 ${num} > 0: ${result}`);
    return result;
};

// 使用示例
console.log(toUpper("hello")); 
// "转换:"hello" -> "HELLO""
// 返回:"HELLO"

console.log(isPositive(-5)); 
// "验证 -5 > 0: false"
// 返回:false

console.log(isPositive(10)); 
// "验证 10 > 0: true"
// 返回:true

🔄 类型别名的特性与注意事项

// 别名不是新建类型 - 只是引用
type ID = UserID;
const newUserId: ID = 1002; // 完全合法
console.log(typeof newUserId); // "number"

// 递归类型别名
type TreeNode = {
    value: string;
    children?: TreeNode[];
};

const tree: TreeNode = {
    value: "根节点",
    children: [
        { value: "子节点1" },
        { 
            value: "子节点2", 
            children: [
                { value: "孙节点" }
            ]
        }
    ]
};

console.log(tree.value); // "根节点"
console.log(tree.children?.[0].value); // "子节点1"
console.log(tree.children?.[1].children?.[0].value); // "孙节点"

// 泛型类型别名
type ApiResponse<T> = {
    success: boolean;
    data: T;
    message: string;       
};

const userResponse: ApiResponse<UserProfile> = {
    success: true,
    data: user,
    message: "获取用户成功"
};

console.log(userResponse.success); // true
console.log(userResponse.data.name); // "技术宅"
console.log(userResponse.message); // "获取用户成功"

🚨 重要提醒:类型别名不会创建新类型,它只是现有类型的引用(就像给文件创建快捷方式)。使用typeof检查时,仍然是原始类型。

🎯 类型别名的应用场景

  • 简化复杂联合类型:让代码更易读
  • 统一接口定义:保持项目类型一致性
  • 函数签名复用:避免重复定义
  • 递归数据结构:树形、链表等结构

7.2 字符串字面量类型——精确到字符的"选择题"

字符串字面量类型(String Literal Types)把字符串从"任意文本"变成"选择题选项",让你的代码更加精确和安全。

🎯 基础字面量类型:限定选项范围

// 1. 状态管理 - 精确的状态定义
type LightStatus = "on" | "off" | "dimmed";
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type Theme = "light" | "dark" | "auto";

// 灯光控制函数
function setLight(status: LightStatus) {
    console.log(`灯光状态设置为:${status}`);
    // 模拟硬件控制
    switch(status) {
        case "on":
            console.log("💡 灯光已开启");
            break;
        case "off":
            console.log("🌙 灯光已关闭");
            break;
        case "dimmed":
            console.log("🔅 灯光已调暗");
            break;
    }
}

// 使用示例
setLight("on");    
// "灯光状态设置为:on"
// "💡 灯光已开启"

setLight("dimmed"); 
// "灯光状态设置为:dimmed"
// "🔅 灯光已调暗"

// 错误示例:不在选项中
// setLight("flash"); // 错误!"flash"不在LightStatus类型中

// 2. HTTP请求处理
function makeRequest(method: HttpMethod, url: string) {
    console.log(`发送 ${method} 请求到:${url}`);
    // 模拟网络请求
    return { method, url, timestamp: Date.now() };
}

const getResult = makeRequest("GET", "/api/users");
console.log(getResult); 
// { method: "GET", url: "/api/users", timestamp: 1704067200000 }

const postResult = makeRequest("POST", "/api/users");
console.log(postResult.method); // "POST"

🏗️ 实战应用:Redux Action系统

// Redux Action类型定义
type ActionType = "ADD_TODO" | "REMOVE_TODO" | "EDIT_TODO" | "TOGGLE_TODO";

interface BaseAction {
    type: ActionType;
    timestamp: number;
}

interface AddTodoAction extends BaseAction {
    type: "ADD_TODO";
    payload: {
        id: string;
        text: string;
        completed: boolean;
    };
}

interface RemoveTodoAction extends BaseAction {
    type: "REMOVE_TODO";
    payload: {
        id: string;
    };
}

type TodoAction = AddTodoAction | RemoveTodoAction;

// Action创建器
function createAddTodoAction(text: string): AddTodoAction {
    const action = {
        type: "ADD_TODO" as const,
        payload: {
            id: `todo_${Date.now()}`,
            text,
            completed: false
        },
        timestamp: Date.now()
    };
    console.log(`创建添加Todo Action:`, action);
    return action;
}

function createRemoveTodoAction(id: string): RemoveTodoAction {
    const action = {
        type: "REMOVE_TODO" as const,
        payload: { id },
        timestamp: Date.now()
    };
    console.log(`创建删除Todo Action:`, action);
    return action;
}

// 使用示例
const addAction = createAddTodoAction("学习TypeScript高级类型");
// "创建添加Todo Action:" { type: "ADD_TODO", payload: { id: "todo_1704067200000", text: "学习TypeScript高级类型", completed: false }, timestamp: 1704067200000 }

const removeAction = createRemoveTodoAction("todo_123");
// "创建删除Todo Action:" { type: "REMOVE_TODO", payload: { id: "todo_123" }, timestamp: 1704067200000 }

console.log(addAction.type); // "ADD_TODO"
console.log(addAction.payload.text); // "学习TypeScript高级类型"

🎨 模板字面量类型(TypeScript 4.1+)

// 模板字面量类型 - 动态生成字符串类型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ButtonEvent = EventName<"click" | "hover" | "focus">; // "onClick" | "onHover" | "onFocus"

// CSS属性生成
type CSSProperty = "margin" | "padding";
type CSSDirection = "top" | "right" | "bottom" | "left";
type CSSPropertyWithDirection = `${CSSProperty}-${CSSDirection}`;
// "margin-top" | "margin-right" | "margin-bottom" | "margin-left" | "padding-top" | "padding-right" | "padding-bottom" | "padding-left"

// 使用示例
function setCSSProperty(property: CSSPropertyWithDirection, value: string) {
    console.log(`设置CSS属性:${property} = ${value}`);
    return { [property]: value };
}

const marginTop = setCSSProperty("margin-top", "10px");
// "设置CSS属性:margin-top = 10px"
console.log(marginTop); // { "margin-top": "10px" }

const paddingLeft = setCSSProperty("padding-left", "20px");
// "设置CSS属性:padding-left = 20px"
console.log(paddingLeft); // { "padding-left": "20px" }

💡 智能提示加成:在VSCode中,当你输入字面量类型时,编辑器会自动提示所有可选值,大大提升开发效率!

7.3 元组类型——数组的"精确版本"

元组(Tuples)是数组的升级版——它精确规定了数组的长度和每个位置的类型,就像给数组穿上了"定制西装"。

📏 基础元组:固定长度和类型

// 1. 基础元组定义
type PersonData = [string, number]; // [姓名, 年龄]
type Coordinate = [number, number]; // [x, y]
type RGB = [number, number, number]; // [红, 绿, 蓝]

// 使用示例
const userData: PersonData = ["张三", 30];
const position: Coordinate = [100, 200];
const redColor: RGB = [255, 0, 0];

console.log(userData[0]); // "张三"
console.log(userData[1]); // 30
console.log(position); // [100, 200]
console.log(`RGB颜色:rgb(${redColor.join(", ")})`); // "RGB颜色:rgb(255, 0, 0)"

// 错误示例:类型或长度不匹配
// const wrongData: PersonData = ["李四", "三十"]; // 错误!第二个元素应为数字
// const wrongData2: PersonData = ["王五"]; // 错误!缺少第二个元素

// 2. 解构赋值的类型安全
const [name, age] = userData;
console.log(`姓名:${name},年龄:${age}`); // "姓名:张三,年龄:30"
console.log(typeof name); // "string"
console.log(typeof age); // "number"

const [x, y] = position;
console.log(`坐标:(${x}, ${y})`); // "坐标:(100, 200)"

const [r, g, b] = redColor;
console.log(`红色分量:${r}`); // "红色分量:255"

🔧 可选元素与剩余元素

// 1. 带可选元素的元组
type RGBA = [number, number, number, number?]; // 最后的alpha通道可选

const white: RGBA = [255, 255, 255];
const semiTransparent: RGBA = [0, 0, 0, 0.5];

console.log(white); // [255, 255, 255]
console.log(semiTransparent); // [0, 0, 0, 0.5]
console.log(white.length); // 3
console.log(semiTransparent.length); // 4

// 颜色处理函数
function formatColor(color: RGBA): string {
    const [r, g, b, a] = color;
    if (a !== undefined) {
        const result = `rgba(${r}, ${g}, ${b}, ${a})`;
        console.log(`格式化RGBA颜色:`, result);
        return result;
    } else {
        const result = `rgb(${r}, ${g}, ${b})`;
        console.log(`格式化RGB颜色:`, result);
        return result;
    }
}

console.log(formatColor(white)); 
// "格式化RGB颜色:" "rgb(255, 255, 255)"
// 返回:"rgb(255, 255, 255)"

console.log(formatColor(semiTransparent)); 
// "格式化RGBA颜色:" "rgba(0, 0, 0, 0.5)"
// 返回:"rgba(0, 0, 0, 0.5)"

// 2. 剩余元素元组 (TypeScript 4.0+)
type Student = [string, ...number[]]; // 姓名 + 任意数量的分数
type DatabaseRow = [number, string, ...any[]]; // ID + 名称 + 其他字段

const tom: Student = ["Tom", 95, 88, 92, 87];
const lucy: Student = ["Lucy", 100];
const bob: Student = ["Bob", 85, 90, 88, 92, 94, 89];

console.log(tom[0]); // "Tom"
console.log(tom.slice(1)); // [95, 88, 92, 87]

// 计算平均分函数
function calculateAverage(student: Student): number {
    const [name, ...scores] = student;
    const average = scores.reduce((sum, score) => sum + score, 0) / scores.length;
    console.log(`${name}的平均分:${average.toFixed(2)}`);
    return average;
}

console.log(calculateAverage(tom)); 
// "Tom的平均分:90.50"
// 返回:90.5

console.log(calculateAverage(lucy)); 
// "Lucy的平均分:100.00"
// 返回:100

🎯 实战应用:React Hooks模拟

// React useState Hook的类型定义
type StateHook<T> = [T, (newValue: T) => void];
type EffectHook = [() => void, any[]];

// 模拟useState实现
function useState<T>(initialValue: T): StateHook<T> {
    let value = initialValue;
    
    const setValue = (newValue: T) => {
        const oldValue = value;
        value = newValue;
        console.log(`状态更新:${oldValue} -> ${newValue}`);
    };
    
    const getter = () => {
        console.log(`获取当前状态:${value}`);
        return value;
    };
    
    return [getter() as T, setValue];
}

// 使用示例
const [count, setCount] = useState(0);
// "获取当前状态:0"

console.log(count); // 0

setCount(5);
// "状态更新:0 -> 5"

const [name, setName] = useState("Alice");
// "获取当前状态:Alice"

console.log(name); // "Alice"

setName("Bob");
// "状态更新:Alice -> Bob"

// 数据库查询结果元组
type QueryResult = [boolean, any[], string?]; // [成功状态, 数据, 错误信息]

function mockQuery(sql: string): QueryResult {
    console.log(`执行SQL:${sql}`);
    
    if (sql.includes("SELECT")) {
        const mockData = [{ id: 1, name: "用户1" }, { id: 2, name: "用户2" }];
        console.log(`查询成功,返回 ${mockData.length} 条记录`);
        return [true, mockData];
    } else {
        console.log(`查询失败:不支持的SQL语句`);
        return [false, [], "不支持的SQL语句"];
    }
}

const [success, data, error] = mockQuery("SELECT * FROM users");
// "执行SQL:SELECT * FROM users"
// "查询成功,返回 2 条记录"

console.log(success); // true
console.log(data.length); // 2
console.log(error); // undefined

const [success2, data2, error2] = mockQuery("DROP TABLE users");
// "执行SQL:DROP TABLE users"
// "查询失败:不支持的SQL语句"

console.log(success2); // false
console.log(data2.length); // 0
console.log(error2); // "不支持的SQL语句"

7.4 枚举类型——常量的"豪华套装"

枚举(Enums)是为数值常量提供语义化名称的最佳工具,就像给一堆数字穿上了"有意义的外衣"。

🔢 数字枚举:自动递增的常量

// 1. 基础数字枚举
enum Direction {
    Up = 1,    // 指定起始值
    Down,      // 自动递增为2
    Left,      // 自动递增为3
    Right      // 自动递增为4
}

// 使用示例
function move(direction: Direction) {
    console.log(`移动方向:${Direction[direction]} (值: ${direction})`);
    
    switch(direction) {
        case Direction.Up:
            console.log("⬆️ 向上移动");
            break;
        case Direction.Down:
            console.log("⬇️ 向下移动");
            break;
        case Direction.Left:
            console.log("⬅️ 向左移动");
            break;
        case Direction.Right:
            console.log("➡️ 向右移动");
            break;
    }
}

move(Direction.Up);
// "移动方向:Up (值: 1)"
// "⬆️ 向上移动"

move(Direction.Right);
// "移动方向:Right (值: 4)"
// "➡️ 向右移动"

// 反向映射
console.log(Direction[1]); // "Up"
console.log(Direction[4]); // "Right"
console.log(Direction.Up); // 1
console.log(Direction.Right); // 4

// 2. 状态码枚举
enum HttpStatus {
    OK = 200,
    NotFound = 404,
    InternalServerError = 500
}

function handleResponse(status: HttpStatus) {
    console.log(`处理HTTP状态码:${status}`);
    
    if (status === HttpStatus.OK) {
        console.log("✅ 请求成功");
    } else if (status === HttpStatus.NotFound) {
        console.log("❌ 资源未找到");
    } else if (status === HttpStatus.InternalServerError) {
        console.log("💥 服务器内部错误");
    }
}

handleResponse(HttpStatus.OK);
// "处理HTTP状态码:200"
// "✅ 请求成功"

handleResponse(HttpStatus.NotFound);
// "处理HTTP状态码:404"
// "❌ 资源未找到"

📝 字符串枚举:语义化的常量

// 1. 媒体类型枚举
enum MediaType {
    JSON = "application/json",
    XML = "application/xml",
    TEXT = "text/plain",
    HTML = "text/html"
}

// HTTP请求处理
function setContentType(type: MediaType) {
    console.log(`设置Content-Type: ${type}`);
    // 模拟设置HTTP头部
    return {
        "Content-Type": type,
        "X-Timestamp": new Date().toISOString()
    };
}

const jsonHeaders = setContentType(MediaType.JSON);
// "设置Content-Type: application/json"
console.log(jsonHeaders);
// { "Content-Type": "application/json", "X-Timestamp": "2024-01-01T12:00:00.000Z" }

const xmlHeaders = setContentType(MediaType.XML);
// "设置Content-Type: application/xml"
console.log(xmlHeaders["Content-Type"]); // "application/xml"

// 2. 日志级别枚举
enum LogLevel {
    ERROR = "error",
    WARN = "warn",
    INFO = "info",
    DEBUG = "debug"
}

function log(level: LogLevel, message: string) {
    const timestamp = new Date().toISOString();
    const logEntry = `[${timestamp}] ${level.toUpperCase()}: ${message}`;
    console.log(logEntry);
    
    // 根据级别选择不同的输出方式
    switch(level) {
        case LogLevel.ERROR:
            console.error("🔴 错误日志");
            break;
        case LogLevel.WARN:
            console.warn("🟡 警告日志");
            break;
        case LogLevel.INFO:
            console.info("🔵 信息日志");
            break;
        case LogLevel.DEBUG:
            console.debug("⚪ 调试日志");
            break;
    }
}

log(LogLevel.INFO, "系统启动成功");
// "[2024-01-01T12:00:00.000Z] INFO: 系统启动成功"
// "🔵 信息日志"

log(LogLevel.ERROR, "数据库连接失败");
// "[2024-01-01T12:00:00.000Z] ERROR: 数据库连接失败"
// "🔴 错误日志"

⚡ 常量枚举:编译时优化

// 常量枚举(编译时完全删除)
const enum Color {
    Red,
    Green,
    Blue
}

// 使用常量枚举
function paintPixel(color: Color) {
    console.log(`绘制像素,颜色值:${color}`);
    
    // 编译后这里会直接使用数字值
    if (color === Color.Red) {
        console.log("🔴 绘制红色像素");
    } else if (color === Color.Green) {
        console.log("🟢 绘制绿色像素");
    } else if (color === Color.Blue) {
        console.log("🔵 绘制蓝色像素");
    }
}

paintPixel(Color.Red);
// "绘制像素,颜色值:0"
// "🔴 绘制红色像素"

paintPixel(Color.Blue);
// "绘制像素,颜色值:2"
// "🔵 绘制蓝色像素"

// 编译后的JavaScript代码中,Color.Red会被替换为0,Color.Blue会被替换为2
console.log(Color.Red); // 编译后:console.log(0);

🆚 现代替代方案:联合类型 vs 枚举

// 方案1:传统枚举
enum TraditionalLogLevel {
    Error,
    Warn,
    Info,
    Debug
}

// 方案2:现代联合类型
const ModernLogLevel = {
    Error: 0,
    Warn: 1,
    Info: 2,
    Debug: 3
} as const;

type ModernLogLevel = typeof ModernLogLevel[keyof typeof ModernLogLevel]; // 0 | 1 | 2 | 3

// 比较两种方案
function compareApproaches() {
    // 传统枚举使用
    const traditionalLevel = TraditionalLogLevel.Error;
    console.log(`传统枚举值:${traditionalLevel}`); // "传统枚举值:0"
    console.log(`传统枚举名称:${TraditionalLogLevel[traditionalLevel]}`); // "传统枚举名称:Error"
    
    // 现代联合类型使用
    const modernLevel = ModernLogLevel.Error;
    console.log(`现代联合类型值:${modernLevel}`); // "现代联合类型值:0"
    
    // 运行时大小比较
    console.log(`传统枚举在运行时存在:${typeof TraditionalLogLevel}`); // "传统枚举在运行时存在:object"
    console.log(`现代联合类型在运行时存在:${typeof ModernLogLevel}`); // "现代联合类型在运行时存在:object"
}

compareApproaches();

⚖️ 选择建议

  • 使用枚举:需要反向映射、与外部API交互、团队习惯枚举
  • 使用联合类型:追求最小运行时开销、现代TypeScript项目、函数式编程风格

7.5 类型保护与类型守卫——类型世界的"安检门"

类型保护(Type Guards)是TypeScript的类型收窄机制,让你在特定代码块内获得更精确的类型信息,就像给代码装上了"智能识别系统"。

🔍 内置类型守卫:基础类型检查

// 1. typeof 守卫 - 处理基本类型
function processValue(value: string | number | boolean) {
    console.log(`处理值:${value},类型:${typeof value}`);
    
    if (typeof value === "string") {
        // 在这个块中,value被收窄为string类型
        const result = value.toUpperCase();
        console.log(`字符串处理结果:${result}`);
        return result;
    } else if (typeof value === "number") {
        // 在这个块中,value被收窄为number类型
        const result = value.toFixed(2);
        console.log(`数字处理结果:${result}`);
        return result;
    } else {
        // 在这个块中,value被收窄为boolean类型
        const result = value ? "真" : "假";
        console.log(`布尔值处理结果:${result}`);
        return result;
    }
}

console.log(processValue("hello"));
// "处理值:hello,类型:string"
// "字符串处理结果:HELLO"
// 返回:"HELLO"

console.log(processValue(3.14159));
// "处理值:3.14159,类型:number"
// "数字处理结果:3.14"
// 返回:"3.14"

console.log(processValue(true));
// "处理值:true,类型:boolean"
// "布尔值处理结果:真"
// 返回:"真"

// 2. instanceof 守卫 - 处理类实例
class Bird {
    fly() {
        console.log("🐦 鸟儿在飞翔");
    }
    
    makeSound() {
        console.log("🎵 鸟儿在歌唱");
    }
}

class Fish {
    swim() {
        console.log("🐟 鱼儿在游泳");
    }
    
    makeSound() {
        console.log("🫧 鱼儿在吐泡泡");
    }
}

class Dog {
    run() {
        console.log("🐕 狗狗在奔跑");
    }
    
    makeSound() {
        console.log("🐕 汪汪汪!");
    }
}

function handleAnimal(animal: Bird | Fish | Dog) {
    console.log(`处理动物:${animal.constructor.name}`);
    
    // 所有动物都有makeSound方法
    animal.makeSound();
    
    if (animal instanceof Bird) {
        // 在这个块中,animal被收窄为Bird类型
        animal.fly();
    } else if (animal instanceof Fish) {
        // 在这个块中,animal被收窄为Fish类型
        animal.swim();
    } else {
        // 在这个块中,animal被收窄为Dog类型
        animal.run();
    }
}

const bird = new Bird();
const fish = new Fish();
const dog = new Dog();

handleAnimal(bird);
// "处理动物:Bird"
// "🎵 鸟儿在歌唱"
// "🐦 鸟儿在飞翔"

handleAnimal(fish);
// "处理动物:Fish"
// "🫧 鱼儿在吐泡泡"
// "🐟 鱼儿在游泳"

handleAnimal(dog);
// "处理动物:Dog"
// "🐕 汪汪汪!"
// "🐕 狗狗在奔跑"

🔑 属性检查守卫:in 操作符

// 接口定义
interface Circle {
    kind: "circle";
    radius: number;
}

interface Square {
    kind: "square";
    sideLength: number;
}

interface Triangle {
    kind: "triangle";
    base: number;
    height: number;
}

type Shape = Circle | Square | Triangle;

// 使用in守卫检查属性存在性
function calculateArea(shape: Shape): number {
    console.log(`计算图形面积,类型:${shape.kind}`);
    
    if ("radius" in shape) {
        // 在这个块中,shape被收窄为Circle类型
        const area = Math.PI * shape.radius ** 2;
        console.log(`圆形面积:π × ${shape.radius}² = ${area.toFixed(2)}`);
        return area;
    } else if ("sideLength" in shape) {
        // 在这个块中,shape被收窄为Square类型
        const area = shape.sideLength ** 2;
        console.log(`正方形面积:${shape.sideLength}² = ${area}`);
        return area;
    } else {
        // 在这个块中,shape被收窄为Triangle类型
        const area = (shape.base * shape.height) / 2;
        console.log(`三角形面积:(${shape.base} × ${shape.height}) ÷ 2 = ${area}`);
        return area;
    }
}

// 测试不同图形
const circle: Circle = { kind: "circle", radius: 5 };
const square: Square = { kind: "square", sideLength: 4 };
const triangle: Triangle = { kind: "triangle", base: 6, height: 8 };

console.log(calculateArea(circle));
// "计算图形面积,类型:circle"
// "圆形面积:π × 5² = 78.54"
// 返回:78.53981633974483

console.log(calculateArea(square));
// "计算图形面积,类型:square"
// "正方形面积:4² = 16"
// 返回:16

console.log(calculateArea(triangle));
// "计算图形面积,类型:triangle"
// "三角形面积:(6 × 8) ÷ 2 = 24"
// 返回:24

🛡️ 自定义类型守卫:类型谓词

// 自定义类型守卫函数
function isCircle(shape: Shape): shape is Circle {
    const result = shape.kind === "circle";
    console.log(`检查是否为圆形:${result}`);
    return result;
}

function isSquare(shape: Shape): shape is Square {
    const result = shape.kind === "square";
    console.log(`检查是否为正方形:${result}`);
    return result;
}

function isTriangle(shape: Shape): shape is Triangle {
    const result = shape.kind === "triangle";
    console.log(`检查是否为三角形:${result}`);
    return result;
}

// 使用自定义类型守卫
function describeShape(shape: Shape) {
    console.log(`描述图形:${shape.kind}`);
    
    if (isCircle(shape)) {
        console.log(`这是一个半径为 ${shape.radius} 的圆形`);
        console.log(`周长:${(2 * Math.PI * shape.radius).toFixed(2)}`);
    } else if (isSquare(shape)) {
        console.log(`这是一个边长为 ${shape.sideLength} 的正方形`);
        console.log(`周长:${4 * shape.sideLength}`);
    } else if (isTriangle(shape)) {
        console.log(`这是一个底边 ${shape.base},高 ${shape.height} 的三角形`);
        // 计算斜边(假设是直角三角形)
        const hypotenuse = Math.sqrt(shape.base ** 2 + shape.height ** 2);
        console.log(`斜边长度:${hypotenuse.toFixed(2)}`);
    }
}

describeShape(circle);
// "描述图形:circle"
// "检查是否为圆形:true"
// "这是一个半径为 5 的圆形"
// "周长:31.42"

describeShape(square);
// "描述图形:square"
// "检查是否为圆形:false"
// "检查是否为正方形:true"
// "这是一个边长为 4 的正方形"
// "周长:16"

// 复杂的类型守卫:检查对象结构
interface User {
    id: number;
    name: string;
    email: string;
}

interface Admin extends User {
    permissions: string[];
    lastLogin: Date;
}

function isAdmin(user: User | Admin): user is Admin {
    const hasPermissions = 'permissions' in user;
    const hasLastLogin = 'lastLogin' in user;
    const result = hasPermissions && hasLastLogin;
    console.log(`检查是否为管理员:${result}`);
    return result;
}

function handleUser(user: User | Admin) {
    console.log(`处理用户:${user.name} (ID: ${user.id})`);
    
    if (isAdmin(user)) {
        console.log(`管理员权限:${user.permissions.join(", ")}`);
        console.log(`最后登录:${user.lastLogin.toISOString()}`);
    } else {
        console.log(`普通用户,邮箱:${user.email}`);
    }
}

const regularUser: User = {
    id: 1,
    name: "张三",
    email: "zhangsan@example.com"
};

const adminUser: Admin = {
    id: 2,
    name: "李四",
    email: "lisi@example.com",
    permissions: ["read", "write", "delete"],
    lastLogin: new Date()
};

handleUser(regularUser);
// "处理用户:张三 (ID: 1)"
// "检查是否为管理员:false"
// "普通用户,邮箱:zhangsan@example.com"

handleUser(adminUser);
// "处理用户:李四 (ID: 2)"
// "检查是否为管理员:true"
// "管理员权限:read, write, delete"
// "最后登录:2024-01-01T12:00:00.000Z"

7.6 映射类型——类型转换的"流水线工厂"

映射类型(Mapped Types)让你能像操作数据一样批量转换类型,就像拥有了一条专门生产类型的"智能流水线"。

🏭 内置映射类型:TypeScript的"标准工具"

// 基础用户接口
interface User {
    id: number;
    name: string;
    email: string;
    age: number;
    isActive: boolean;
}

// 1. Partial<T> - 将所有属性变为可选
type PartialUser = Partial<User>;
/* 等价于:
{
    id?: number;
    name?: string;
    email?: string;
    age?: number;
    isActive?: boolean;
}
*/

// 使用Partial类型
function updateUser(id: number, updates: PartialUser) {
    console.log(`更新用户 ${id},更新字段:`, Object.keys(updates));
    
    // 模拟数据库更新
    const updatedFields: string[] = [];
    if (updates.name !== undefined) {
        console.log(`更新姓名:${updates.name}`);
        updatedFields.push("name");
    }
    if (updates.email !== undefined) {
        console.log(`更新邮箱:${updates.email}`);
        updatedFields.push("email");
    }
    if (updates.age !== undefined) {
        console.log(`更新年龄:${updates.age}`);
        updatedFields.push("age");
    }
    if (updates.isActive !== undefined) {
        console.log(`更新状态:${updates.isActive ? "激活" : "禁用"}`);
        updatedFields.push("isActive");
    }
    
    return { success: true, updatedFields };
}

const result1 = updateUser(1, { name: "新姓名", age: 25 });
// "更新用户 1,更新字段:" ["name", "age"]
// "更新姓名:新姓名"
// "更新年龄:25"
console.log(result1); // { success: true, updatedFields: ["name", "age"] }

const result2 = updateUser(2, { isActive: false });
// "更新用户 2,更新字段:" ["isActive"]
// "更新状态:禁用"
console.log(result2); // { success: true, updatedFields: ["isActive"] }

// 2. Required<T> - 将所有属性变为必需
interface OptionalConfig {
    host?: string;
    port?: number;
    ssl?: boolean;
    timeout?: number;
}

type RequiredConfig = Required<OptionalConfig>;
/* 等价于:
{
    host: string;
    port: number;
    ssl: boolean;
    timeout: number;
}
*/

function createConnection(config: RequiredConfig) {
    console.log(`创建连接:${config.ssl ? 'https' : 'http'}://${config.host}:${config.port}`);
    console.log(`超时设置:${config.timeout}ms`);
    
    return {
        url: `${config.ssl ? 'https' : 'http'}://${config.host}:${config.port}`,
        timeout: config.timeout,
        connected: true
    };
}

const connection = createConnection({
    host: "api.example.com",
    port: 443,
    ssl: true,
    timeout: 5000
});
// "创建连接:https://api.example.com:443"
// "超时设置:5000ms"
console.log(connection.connected); // true

// 3. Readonly<T> - 将所有属性变为只读
type ReadonlyUser = Readonly<User>;

function createReadonlyUser(userData: User): ReadonlyUser {
    console.log(`创建只读用户:${userData.name}`);
    const readonlyUser = Object.freeze({ ...userData });
    console.log(`用户已设为只读模式`);
    return readonlyUser;
}

const originalUser: User = {
    id: 1,
    name: "张三",
    email: "zhangsan@example.com",
    age: 30,
    isActive: true
};

const readonlyUser = createReadonlyUser(originalUser);
// "创建只读用户:张三"
// "用户已设为只读模式"

console.log(readonlyUser.name); // "张三"
// readonlyUser.name = "李四"; // 错误!无法修改只读属性

🎨 自定义映射类型:打造专属工具

// 1. 生成Getter方法类型
type Getters<T> = {
    [K in keyof T as `get${Capitalize<string & K>}`]: () => T[K];
};

type UserGetters = Getters<User>;
/* 等价于:
{
    getId: () => number;
    getName: () => string;
    getEmail: () => string;
    getAge: () => number;
    getIsActive: () => boolean;
}
*/

// 实现Getter类
class UserWithGetters implements UserGetters {
    constructor(private user: User) {
        console.log(`创建带Getter的用户:${user.name}`);
    }
    
    getId(): number {
        console.log(`获取ID:${this.user.id}`);
        return this.user.id;
    }
    
    getName(): string {
        console.log(`获取姓名:${this.user.name}`);
        return this.user.name;
    }
    
    getEmail(): string {
        console.log(`获取邮箱:${this.user.email}`);
        return this.user.email;
    }
    
    getAge(): number {
        console.log(`获取年龄:${this.user.age}`);
        return this.user.age;
    }
    
    getIsActive(): boolean {
        console.log(`获取状态:${this.user.isActive ? "激活" : "禁用"}`);
        return this.user.isActive;
    }
}

const userWithGetters = new UserWithGetters(originalUser);
// "创建带Getter的用户:张三"

console.log(userWithGetters.getName()); 
// "获取姓名:张三"
// 返回:"张三"

console.log(userWithGetters.getAge()); 
// "获取年龄:30"
// 返回:30

// 2. 过滤特定类型的属性
type StringProperties<T> = {
    [K in keyof T as T[K] extends string ? K : never]: T[K];
};

type UserStringProps = StringProperties<User>;
/* 等价于:
{
    name: string;
    email: string;
}
*/

function extractStringProperties(user: User): UserStringProps {
    console.log(`提取用户的字符串属性`);
    const stringProps = {
        name: user.name,
        email: user.email
    };
    console.log(`提取的属性:`, stringProps);
    return stringProps;
}

const stringProps = extractStringProperties(originalUser);
// "提取用户的字符串属性"
// "提取的属性:" { name: "张三", email: "zhangsan@example.com" }
console.log(stringProps.name); // "张三"
console.log(stringProps.email); // "zhangsan@example.com"

// 3. 创建可空版本的类型
type Nullable<T> = {
    [K in keyof T]: T[K] | null;
};

type NullableUser = Nullable<User>;
/* 等价于:
{
    id: number | null;
    name: string | null;
    email: string | null;
    age: number | null;
    isActive: boolean | null;
}
*/

function createNullableUser(partial: Partial<User>): NullableUser {
    console.log(`创建可空用户,输入字段:`, Object.keys(partial));
    
    const nullableUser: NullableUser = {
        id: partial.id ?? null,
        name: partial.name ?? null,
        email: partial.email ?? null,
        age: partial.age ?? null,
        isActive: partial.isActive ?? null
    };
    
    console.log(`创建的可空用户:`, nullableUser);
    return nullableUser;
}

const nullableUser = createNullableUser({ name: "测试用户", age: 25 });
// "创建可空用户,输入字段:" ["name", "age"]
// "创建的可空用户:" { id: null, name: "测试用户", email: null, age: 25, isActive: null }

console.log(nullableUser.name); // "测试用户"
console.log(nullableUser.id); // null

🔧 高级映射类型:复杂转换

// 1. 深度只读类型
type DeepReadonly<T> = {
    readonly [K in keyof T]: T[K] extends object ? DeepReadonly<T[K]> : T[K];
};

interface NestedUser {
    id: number;
    profile: {
        name: string;
        contact: {
            email: string;
            phone: string;
        };
    };
    preferences: {
        theme: string;
        language: string;
    };
}

type DeepReadonlyUser = DeepReadonly<NestedUser>;

function createDeepReadonlyUser(user: NestedUser): DeepReadonlyUser {
    console.log(`创建深度只读用户:${user.profile.name}`);
    
    // 深度冻结对象
    function deepFreeze<T>(obj: T): T {
        Object.getOwnPropertyNames(obj).forEach(prop => {
            const value = (obj as any)[prop];
            if (value && typeof value === 'object') {
                deepFreeze(value);
            }
        });
        return Object.freeze(obj);
    }
    
    const frozenUser = deepFreeze({ ...user });
    console.log(`用户已深度冻结`);
    return frozenUser as DeepReadonlyUser;
}

const nestedUser: NestedUser = {
    id: 1,
    profile: {
        name: "深度用户",
        contact: {
            email: "deep@example.com",
            phone: "123-456-7890"
        }
    },
    preferences: {
        theme: "dark",
        language: "zh-CN"
    }
};

const deepReadonlyUser = createDeepReadonlyUser(nestedUser);
// "创建深度只读用户:深度用户"
// "用户已深度冻结"

console.log(deepReadonlyUser.profile.name); // "深度用户"
console.log(deepReadonlyUser.profile.contact.email); // "deep@example.com"
// deepReadonlyUser.profile.name = "新名字"; // 错误!深度只读
// deepReadonlyUser.profile.contact.email = "new@example.com"; // 错误!深度只读

// 2. 条件映射类型
type ApiEndpoints<T> = {
    [K in keyof T as T[K] extends Function ? `api${Capitalize<string & K>}` : never]: T[K];
};

interface UserService {
    create: (user: User) => Promise<User>;
    update: (id: number, user: Partial<User>) => Promise<User>;
    delete: (id: number) => Promise<void>;
    name: string; // 这个不是函数,会被过滤掉
    version: number; // 这个也不是函数,会被过滤掉
}

type UserApiEndpoints = ApiEndpoints<UserService>;
/* 等价于:
{
    apiCreate: (user: User) => Promise<User>;
    apiUpdate: (id: number, user: Partial<User>) => Promise<User>;
    apiDelete: (id: number) => Promise<void>;
}
*/

class UserApi implements UserApiEndpoints {
    apiCreate(user: User): Promise<User> {
        console.log(`API: 创建用户 ${user.name}`);
        return Promise.resolve({ ...user, id: Date.now() });
    }
    
    apiUpdate(id: number, user: Partial<User>): Promise<User> {
        console.log(`API: 更新用户 ${id},字段:`, Object.keys(user));
        return Promise.resolve({ id, ...user } as User);
    }
    
    apiDelete(id: number): Promise<void> {
        console.log(`API: 删除用户 ${id}`);
        return Promise.resolve();
    }
}

const userApi = new UserApi();

// 异步测试
async function testUserApi() {
    const newUser = await userApi.apiCreate({
        id: 0, // 会被覆盖
        name: "API用户",
        email: "api@example.com",
        age: 28,
        isActive: true
    });
    // "API: 创建用户 API用户"
    console.log(`创建的用户ID:${newUser.id}`);
    
    await userApi.apiUpdate(newUser.id, { age: 29 });
    // "API: 更新用户 1704067200000,字段:" ["age"]
    
    await userApi.apiDelete(newUser.id);
    // "API: 删除用户 1704067200000"
}

testUserApi();

7.7 条件类型——类型系统的"智能决策器"

条件类型(Conditional Types)让类型也能拥有逻辑判断能力,就像给类型系统装上了"人工智能"。

🧠 基础条件类型:类型的三目运算符

// 基础语法:T extends U ? X : Y
type IsString<T> = T extends string ? true : false;
type IsNumber<T> = T extends number ? true : false;
type IsArray<T> = T extends any[] ? true : false;

// 测试类型判断
type Test1 = IsString<"hello">; // true
type Test2 = IsString<42>;      // false
type Test3 = IsNumber<100>;     // true
type Test4 = IsArray<string[]>; // true

// 实际使用示例
function processData<T>(data: T): T extends string ? string : T extends number ? number : unknown {
    console.log(`处理数据:${data},类型:${typeof data}`);
    
    if (typeof data === "string") {
        const result = data.toUpperCase();
        console.log(`字符串处理结果:${result}`);
        return result as any;
    } else if (typeof data === "number") {
        const result = data * 2;
        console.log(`数字处理结果:${result}`);
        return result as any;
    } else {
        console.log(`未知类型,原样返回`);
        return data as any;
    }
}

const stringResult = processData("hello");
// "处理数据:hello,类型:string"
// "字符串处理结果:HELLO"
console.log(stringResult); // "HELLO"

const numberResult = processData(42);
// "处理数据:42,类型:number"
// "数字处理结果:84"
console.log(numberResult); // 84

const unknownResult = processData(true);
// "处理数据:true,类型:boolean"
// "未知类型,原样返回"
console.log(unknownResult); // true

🔄 分布式条件类型:批量处理联合类型

// 分布式条件类型 - 自动分发到联合类型的每个成员
type ToArray<T> = T extends any ? T[] : never;

// 当T是联合类型时,条件类型会分布到每个成员
type StringOrNumberArray = ToArray<string | number>; // string[] | number[]
type BooleanArray = ToArray<boolean>; // boolean[]

// 实际应用:过滤联合类型
type NonNullable<T> = T extends null | undefined ? never : T;

type MaybeString = string | null | undefined;
type DefinitelyString = NonNullable<MaybeString>; // string

// 使用示例
function filterNullable<T>(value: T): NonNullable<T> | null {
    if (value === null || value === undefined) {
        console.log(`过滤掉空值:${value}`);
        return null;
    }
    console.log(`保留有效值:${value}`);
    return value as NonNullable<T>;
}

const result1 = filterNullable("hello");
// "保留有效值:hello"
console.log(result1); // "hello"

const result2 = filterNullable(null);
// "过滤掉空值:null"
console.log(result2); // null

const result3 = filterNullable(undefined);
// "过滤掉空值:undefined"
console.log(result3); // null

🔍 类型推断:infer 关键字的魔法

// 1. 提取函数返回类型
type ReturnType<T> = T extends (...args: any[]) => infer R ? R : any;

type StringFunction = () => string;
type NumberFunction = (x: number) => number;
type VoidFunction = () => void;

type StringReturn = ReturnType<StringFunction>; // string
type NumberReturn = ReturnType<NumberFunction>; // number
type VoidReturn = ReturnType<VoidFunction>; // void

// 实际使用
function createTypedFunction<F extends (...args: any[]) => any>(
    fn: F
): (...args: Parameters<F>) => ReturnType<F> {
    return (...args) => {
        console.log(`调用函数,参数:`, args);
        const result = fn(...args);
        console.log(`函数返回:`, result);
        return result;
    };
}

const add = (a: number, b: number) => a + b;
const typedAdd = createTypedFunction(add);

const sum = typedAdd(5, 3);
// "调用函数,参数:" [5, 3]
// "函数返回:" 8
console.log(sum); // 8

// 2. 提取数组元素类型
type ArrayElement<T> = T extends (infer U)[] ? U : never;

type StringArrayElement = ArrayElement<string[]>; // string
type NumberArrayElement = ArrayElement<number[]>; // number
type MixedArrayElement = ArrayElement<(string | number)[]>; // string | number

// 实际应用:数组处理函数
function processArray<T extends any[]>(
    arr: T,
    processor: (item: ArrayElement<T>) => ArrayElement<T>
): T {
    console.log(`处理数组,长度:${arr.length}`);
    const result = arr.map(item => {
        const processed = processor(item);
        console.log(`处理项目:${item} -> ${processed}`);
        return processed;
    }) as T;
    console.log(`处理完成`);
    return result;
}

const numbers = [1, 2, 3, 4, 5];
const doubled = processArray(numbers, x => x * 2);
// "处理数组,长度:5"
// "处理项目:1 -> 2"
// "处理项目:2 -> 4"
// "处理项目:3 -> 6"
// "处理项目:4 -> 8"
// "处理项目:5 -> 10"
// "处理完成"
console.log(doubled); // [2, 4, 6, 8, 10]

const strings = ["hello", "world", "typescript"];
const uppercased = processArray(strings, s => s.toUpperCase());
// "处理数组,长度:3"
// "处理项目:hello -> HELLO"
// "处理项目:world -> WORLD"
// "处理项目:typescript -> TYPESCRIPT"
// "处理完成"
console.log(uppercased); // ["HELLO", "WORLD", "TYPESCRIPT"]

// 3. 提取Promise的值类型
type Awaited<T> = T extends Promise<infer U> ? U : T;

type StringPromise = Promise<string>;
type NumberPromise = Promise<number>;
type NestedPromise = Promise<Promise<boolean>>;

type StringValue = Awaited<StringPromise>; // string
type NumberValue = Awaited<NumberPromise>; // number
type BooleanValue = Awaited<NestedPromise>; // Promise<boolean> (只解包一层)

// 深度解包Promise
type DeepAwaited<T> = T extends Promise<infer U> ? DeepAwaited<U> : T;

type DeepBooleanValue = DeepAwaited<NestedPromise>; // boolean

// 实际应用:异步函数包装器
async function wrapAsync<T extends Promise<any>>(
    promise: T
): Promise<{ success: boolean; data: Awaited<T> | null; error: string | null }> {
    console.log(`包装异步操作`);
    try {
        const data = await promise;
        console.log(`异步操作成功:`, data);
        return { success: true, data, error: null };
    } catch (error) {
        console.log(`异步操作失败:`, error);
        return { success: false, data: null, error: String(error) };
    }
}

// 测试异步包装器
async function testAsyncWrapper() {
    const successPromise = Promise.resolve("成功数据");
    const failPromise = Promise.reject("失败原因");
    
    const result1 = await wrapAsync(successPromise);
    // "包装异步操作"
    // "异步操作成功:" "成功数据"
    console.log(result1); // { success: true, data: "成功数据", error: null }
    
    const result2 = await wrapAsync(failPromise);
    // "包装异步操作"
    // "异步操作失败:" "失败原因"
    console.log(result2); // { success: false, data: null, error: "失败原因" }
}

testAsyncWrapper();

🎯 实战应用:智能表单验证系统

// 字段类型定义
type FieldType = "text" | "number" | "email" | "date" | "boolean";

// 基础字段配置
interface BaseFieldConfig {
    name: string;
    type: FieldType;
    required?: boolean;
    label?: string;
}

// 根据字段类型确定值类型的条件类型
type FieldValue<T extends BaseFieldConfig> = 
    T extends { type: "number" } ? number :
    T extends { type: "date" } ? Date :
    T extends { type: "boolean" } ? boolean :
    string; // 默认为string (text, email)

// 根据required属性确定是否可选
type FieldValueWithRequired<T extends BaseFieldConfig> = 
    T extends { required: true } 
        ? FieldValue<T> 
        : FieldValue<T> | undefined;

// 表单值类型生成器
type FormValues<Fields extends readonly BaseFieldConfig[]> = {
    [K in Fields[number]["name"]]: FieldValueWithRequired<
        Extract<Fields[number], { name: K }>
    >;
};

// 验证规则类型
type ValidationRule<T> = {
    validate: (value: T) => boolean;
    message: string;
};

type FieldValidation<T extends BaseFieldConfig> = {
    [K in T["name"]]: ValidationRule<FieldValueWithRequired<T>>[];
};

// 表单配置示例
const userFormConfig = [
    { name: "username", type: "text", required: true, label: "用户名" },
    { name: "email", type: "email", required: true, label: "邮箱" },
    { name: "age", type: "number", required: false, label: "年龄" },
    { name: "birthdate", type: "date", required: false, label: "生日" },
    { name: "isActive", type: "boolean", required: true, label: "是否激活" }
] as const;

type UserFormValues = FormValues<typeof userFormConfig>;
/* 等价于:
{
    username: string;
    email: string;
    age: number | undefined;
    birthdate: Date | undefined;
    isActive: boolean;
}
*/

// 验证器实现
class FormValidator<T extends readonly BaseFieldConfig[]> {
    constructor(private config: T) {
        console.log(`创建表单验证器,字段数量:${config.length}`);
    }
    
    validate(values: FormValues<T>): { isValid: boolean; errors: string[] } {
        console.log(`开始验证表单`);
        const errors: string[] = [];
        
        for (const field of this.config) {
            const value = (values as any)[field.name];
            console.log(`验证字段 ${field.name}${value}`);
            
            // 检查必填字段
            if (field.required && (value === undefined || value === null || value === "")) {
                const error = `${field.label || field.name} 是必填字段`;
                console.log(`验证失败:${error}`);
                errors.push(error);
                continue;
            }
            
            // 类型特定验证
            if (value !== undefined && value !== null && value !== "") {
                switch (field.type) {
                    case "email":
                        const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
                        if (!emailRegex.test(String(value))) {
                            const error = `${field.label || field.name} 格式不正确`;
                            console.log(`验证失败:${error}`);
                            errors.push(error);
                        }
                        break;
                    case "number":
                        if (typeof value !== "number" || isNaN(value)) {
                            const error = `${field.label || field.name} 必须是有效数字`;
                            console.log(`验证失败:${error}`);
                            errors.push(error);
                        }
                        break;
                    case "date":
                        if (!(value instanceof Date) || isNaN(value.getTime())) {
                            const error = `${field.label || field.name} 必须是有效日期`;
                            console.log(`验证失败:${error}`);
                            errors.push(error);
                        }
                        break;
                }
            }
            
            console.log(`字段 ${field.name} 验证通过`);
        }
        
        const isValid = errors.length === 0;
        console.log(`表单验证${isValid ? "成功" : "失败"},错误数量:${errors.length}`);
        return { isValid, errors };
    }
}

// 使用示例
const validator = new FormValidator(userFormConfig);
// "创建表单验证器,字段数量:5"

// 测试有效数据
const validData: UserFormValues = {
    username: "techuser",
    email: "tech@example.com",
    age: 25,
    birthdate: new Date("1998-01-01"),
    isActive: true
};

const result1 = validator.validate(validData);
// "开始验证表单"
// "验证字段 username:techuser"
// "字段 username 验证通过"
// "验证字段 email:tech@example.com"
// "字段 email 验证通过"
// "验证字段 age:25"
// "字段 age 验证通过"
// "验证字段 birthdate:Wed Jan 01 1998 00:00:00 GMT+0800"
// "字段 birthdate 验证通过"
// "验证字段 isActive:true"
// "字段 isActive 验证通过"
// "表单验证成功,错误数量:0"
console.log(result1); // { isValid: true, errors: [] }

// 测试无效数据
const invalidData: UserFormValues = {
    username: "", // 必填但为空
    email: "invalid-email", // 格式错误
    age: undefined, // 可选字段
    birthdate: undefined, // 可选字段
    isActive: true
};

const result2 = validator.validate(invalidData);
// "开始验证表单"
// "验证字段 username:"
// "验证失败:用户名 是必填字段"
// "验证字段 email:invalid-email"
// "验证失败:邮箱 格式不正确"
// "验证字段 age:undefined"
// "字段 age 验证通过"
// "验证字段 birthdate:undefined"
// "字段 birthdate 验证通过"
// "验证字段 isActive:true"
// "字段 isActive 验证通过"
// "表单验证失败,错误数量:2"
console.log(result2); // { isValid: false, errors: ["用户名 是必填字段", "邮箱 格式不正确"] }

🎯 高级类型最佳实践与设计模式

📋 类型设计原则对比表

原则 说明 好的例子 避免的例子
类型安全优先 优先保证类型安全,避免any type ID = string | number type ID = any
语义化命名 类型名称要有明确含义 type UserRole = "admin" | "user" type T1 = "a" | "b"
组合优于继承 使用联合类型和交叉类型 type Response<T> = Success<T> | Error 复杂的类继承链
渐进式增强 从简单类型开始,逐步增强 先定义基础类型,再扩展 一开始就定义复杂类型
可读性重要 复杂类型要有注释和示例 带有详细注释的类型定义 没有说明的复杂类型

🛠️ 实用工具类型库

// 1. 深度部分类型
type DeepPartial<T> = {
    [P in keyof T]?: T[P] extends object ? DeepPartial<T[P]> : T[P];
};

// 2. 深度必需类型
type DeepRequired<T> = {
    [P in keyof T]-?: T[P] extends object ? DeepRequired<T[P]> : T[P];
};

// 3. 选择性Pick
type PickByType<T, U> = {
    [K in keyof T as T[K] extends U ? K : never]: T[K];
};

// 4. 排除性Omit
type OmitByType<T, U> = {
    [K in keyof T as T[K] extends U ? never : K]: T[K];
};

// 5. 可空类型转换
type Nullish<T> = {
    [K in keyof T]: T[K] | null | undefined;
};

// 6. 函数参数提取
type Head<T extends any[]> = T extends [infer H, ...any[]] ? H : never;
type Tail<T extends any[]> = T extends [any, ...infer R] ? R : never;

// 使用示例
interface ComplexUser {
    id: number;
    profile: {
        name: string;
        settings: {
            theme: string;
            notifications: boolean;
        };
    };
    permissions: string[];
    createdAt: Date;
}

type PartialUser = DeepPartial<ComplexUser>;
type StringFields = PickByType<ComplexUser, string>;
type NonStringFields = OmitByType<ComplexUser, string>;

console.log("工具类型演示完成"); // "工具类型演示完成"

🎨 类型体操进阶挑战

// 挑战1:字符串操作类型
type Reverse<S extends string> = S extends `${infer First}${infer Rest}` 
    ? `${Reverse<Rest>}${First}` 
    : S;

type ReversedHello = Reverse<"hello">; // "olleh"

// 挑战2:数组长度计算
type Length<T extends readonly any[]> = T['length'];
type ArrayLength = Length<[1, 2, 3, 4, 5]>; // 5

// 挑战3:对象键值转换
type Flip<T extends Record<string, string>> = {
    [K in keyof T as T[K]]: K;
};

type Original = { a: "x", b: "y", c: "z" };
type Flipped = Flip<Original>; // { x: "a", y: "b", z: "c" }

// 挑战4:递归计数器
type Counter<N extends number, C extends any[] = []> = 
    C['length'] extends N ? C : Counter<N, [...C, any]>;

type FiveItems = Counter<5>; // [any, any, any, any, any]

console.log("类型体操挑战完成"); // "类型体操挑战完成"

📚 本章核心收获总结

🎯 掌握的核心技能

  1. 类型别名 🏷️

    • 简化复杂类型定义
    • 提高代码可读性和维护性
    • 支持泛型和递归定义
  2. 字符串字面量类型 🎯

    • 精确控制字符串值范围
    • 模板字面量类型的强大功能
    • 与Redux、状态管理的完美结合
  3. 元组类型 📏

    • 固定长度和类型的数组
    • 支持可选元素和剩余元素
    • React Hooks等场景的类型安全
  4. 枚举类型 🔢

    • 数字枚举和字符串枚举
    • 常量枚举的编译时优化
    • 现代联合类型替代方案
  5. 类型保护 🛡️

    • typeof、instanceof、in守卫
    • 自定义类型谓词函数
    • 运行时类型收窄机制
  6. 映射类型 🏭

    • 内置工具类型的使用
    • 自定义映射类型的创建
    • 批量类型转换的强大能力
  7. 条件类型 🧠

    • 类型系统的逻辑判断
    • infer关键字的类型推断
    • 分布式条件类型的特性

🚀 实战应用场景

  • API接口设计:使用映射类型生成请求/响应类型
  • 状态管理:字面量类型确保Action类型安全
  • 表单验证:条件类型实现智能表单系统
  • 工具函数:类型保护确保函数参数安全
  • 组件开发:元组类型支持Hooks等模式

💡 进阶学习建议

  1. 多练习类型体操:提升类型思维能力
  2. 阅读优秀库源码:学习实际应用模式
  3. 关注TypeScript更新:掌握最新特性
  4. 结合实际项目:在真实场景中应用
  5. 分享交流经验:与团队共同成长

🎉 恭喜你! 你已经掌握了TypeScript高级类型的核心技能,这些强大的类型工具将让你的代码更加安全、优雅和高效。接下来,我们将探索模块与命名空间的世界,学习如何组织大型TypeScript项目!

在 TypeScript 项目中高效使用 node_modules 中的全局类型

作者 前端微白
2025年7月1日 15:01

在 TypeScript 项目中,全局类型定义的管理和使用是提升开发效率和代码质量的关键。本文详细解析如何从 node_modules 引入全局类型到你的 src 目录中,解决常见问题并分享最佳实践。

理解全局类型与模块类型

在 TypeScript 中,类型系统主要分为两种形式:

graph LR
    A[TypeScript 类型] --> B[全局类型]
    A --> C[模块类型]
    B --> D[全局可用<br>无需导入]
    C --> E[需要显式导入<br>import type]

全局类型的特点:

  • 无需导入:在任何文件中直接可用
  • 自动合并:同名接口会自动合并
  • 环境声明:通常通过 .d.ts 文件定义

配置 tsconfig.json 正确引用全局类型

正确配置 tsconfig.json 是使用全局类型的基础:

{
  "compilerOptions": {
    "types": ["node", "lodash", "express"],
    "typeRoots": [
      "./node_modules/@types",
      "./global_types"
    ]
  },
  "include": ["src/**/*.ts", "src/**/*.tsx"],
  "exclude": ["node_modules"]
}

配置详解:

  • types:显式列出要包含的全局类型包
  • typeRoots:定义类型查找路径(默认包含 node_modules/@types)
  • include:指定需要编译的文件范围

三种引用全局类型的方法

方法1:直接通过 npm 包引用(推荐)

步骤:

  1. 安装带有全局类型声明的包:

    npm install --save-dev @types/lodash @types/express
    
  2. 在 tsconfig.json 中配置:

    {
      "compilerOptions": {
        "types": ["lodash", "express"]
      }
    }
    
  3. 在项目中直接使用全局类型:

    // src/main.ts
    const user: Express.User = { 
      id: 1, 
      name: "John" 
    };
    
    const sortedItems = _.sortBy([3, 1, 2]);
    

方法2:手动声明全局类型扩展

当需要扩展第三方库的类型或自定义全局类型时:

  1. 创建 src/global.d.ts 文件:

    // 扩展 Express 的 Request 接口
    declare namespace Express {
      interface Request {
        userId: number;
        requestTime: Date;
      }
    }
    
    // 自定义全局类型
    interface GlobalConfig {
      apiBaseUrl: string;
      version: string;
    }
    
    // 通过模块扩充声明全局变量
    declare global {
      const appConfig: GlobalConfig;
    }
    
  2. 在项目中直接使用:

    // src/routes/auth.ts
    import { Request, Response } from 'express';
    
    export const getProfile = (req: Request, res: Response) => {
      console.log(req.userId); // 扩展的属性
      console.log(appConfig.version); // 全局变量
      // ...
    };
    

方法3:通过三斜线指令引用特定位置(传统方式)

/// <reference types="jquery" />
/// <reference path="../node_modules/custom-lib/types/index.d.ts" />

现代TypeScript项目中通常不再推荐使用三斜线指令,优先使用 tsconfig.json 配置

解决常见问题与冲突

问题1:全局类型未被正确识别

解决方案步骤:

  1. 确认包已正确安装:node_modules/@types/ 下存在对应包
  2. tsconfig.jsontypes 字段中添加包名
  3. 重启TypeScript服务(VSCode中按Ctrl+Shift+P > Restart TS Server)

问题2:全局类型冲突处理

当多个模块定义相同全局类型时:

// 使用 declare module 合并而不是覆盖
declare module 'express' {
  interface Request {
     customProperty: string;
  }
}

// 使用模块重命名解决冲突
import { User as AuthUser } from '@auth/types';
import { User as DbUser } from '@db/types';

type UnifiedUser = AuthUser & DbUser;

问题3:自定义全局类型优先级

在项目中创建 types/ 目录存放自定义类型:

project-root/
├── src/
│   └── ...
├── types/
│   ├── global.d.ts
│   └── custom-types/
└── tsconfig.json

配置 tsconfig.json

{
  "compilerOptions": {
    "typeRoots": [
      "./node_modules/@types",
      "./types"
    ]
  }
}

最佳实践指南

1. 优先选择 @types 命名空间包

# 安装类型定义
npm install --save-dev @types/react @types/node

2. 模块类型 vs 全局类型使用场景

场景 推荐方式
库的类型定义 模块类型 import type { ... }
框架扩展 (Express, Vue) 全局类型声明
项目配置/全局常量 全局接口声明
跨组件共享类型 模块类型导出/导入

3. 自定义全局类型命名规范

// ✅ 推荐使用前缀避免冲突
interface MyApp_UserPreferences {
  theme: 'dark' | 'light';
  fontSize: number;
}

// ✅ 使用命名空间组织
declare namespace MyApp {
  interface Config {
    apiEndpoint: string;
    debugMode: boolean;
  }
}

// ❌ 避免泛型全局名称
interface Config {} // 可能与其他库冲突

4. 版本控制与类型同步

添加预安装脚本确保类型与依赖同步:

// package.json
{
  "scripts": {
    "preinstall": "npm list @types/react || npm install @types/react@^18"
  }
}

实战案例:Express项目中扩展全局类型

项目结构:

express-api/
├── src/
│   ├── app.ts
│   ├── middleware/
│   │   └── auth.ts
│   └── routes/
│       └── users.ts
├── types/
│   └── express.d.ts
└── tsconfig.json

扩展步骤:

  1. 创建类型扩展文件 types/express.d.ts:

    import { User } from '../src/models/user';
    
    declare global {
      namespace Express {
        interface Request {
          user?: User;
          startTime: number;
        }
      }
    }
    
  2. 配置 tsconfig.json:

    {
      "compilerOptions": {
        "typeRoots": ["./node_modules/@types", "./types"]
      }
    }
    
  3. 在中间件中使用扩展属性:

    // src/middleware/auth.ts
    import { Request, Response, NextFunction } from 'express';
    
    export const authMiddleware = (
      req: Request, 
      res: Response, 
      next: NextFunction
    ) => {
      req.startTime = Date.now();
      
      // 模拟用户验证
      req.user = {
        id: 1,
        name: 'John Doe',
        role: 'admin'
      };
      
      next();
    };
    
  4. 在路由中安全访问扩展属性:

    // src/routes/users.ts
    import { Router } from 'express';
    import { authMiddleware } from '../middleware/auth';
    
    const router = Router();
    
    router.use(authMiddleware);
    
    router.get('/profile', (req, res) => {
      if (!req.user) {
        return res.status(401).send('Unauthorized');
      }
      
      const processingTime = Date.now() - req.startTime;
      res.json({
        user: req.user,
        processingTime: `${processingTime}ms`
      });
    });
    
    export default router;
    

调试与验证类型声明

检查类型覆盖范围的命令

npx tsc --noEmit --listFiles | grep .d.ts

生成类型声明地图

// tsconfig.json
{
  "compilerOptions": {
    "declaration": true,
    "declarationMap": true,
    "sourceMap": true
  }
}

小结

graph TD
    A[项目开始] --> B{需要全局类型?}
    B -->|是| C[查看官方类型库是否可用]
    B -->|否| D[使用模块类型导入]
    C --> E[安装官方类型包]
    E --> F[配置 tsconfig.json]
    F --> H[在 types 字段添加包名]
    C -->|无可用| G[创建 custom.d.ts]
    G --> I[声明全局类型]
    I --> J[在 typeRoots 添加自定义路径]
    H --> K[类型生效]
    J --> K
    K --> L[严格检查类型安全]
    L --> M[编译通过]

通过本文的指导,你可以:

  1. 正确配置项目以使用 node_modules 中的全局类型
  2. 解决类型引用中的常见问题
  3. 扩展第三方库的类型定义
  4. 安全地使用自定义全局类型
  5. 优化类型声明管理策略

遵循这些实践,你的 TypeScript 项目将具有更高的开发效率和更强的类型安全性,同时避免常见的全局类型冲突问题。

TypeScript深度强化第四天

作者 旧时光_
2025年7月1日 13:43

TypeScript 深度强化第四天:高级类型操作、工具类型、类型编程 🧙‍♂️

学会"积木魔法" - 掌握 TypeScript 的类型级编程艺术

📖 今日学习目标

第四天我们将深入 TypeScript 的类型系统精髓,这些是类型级编程的核心技能:

  • 🛠️ 实用工具类型 - TypeScript 内置的类型操作瑞士军刀
  • 🎭 条件类型与 infer - 类型级的逻辑判断和推导
  • 🔄 映射类型 - 批量转换类型的强大工具
  • 📝 模板字面量类型 - 字符串类型的动态构建
  • 🧠 高级类型编程 - 构建复杂的类型转换逻辑
  • 🔍 类型兼容性 - 深入理解 TypeScript 的类型推断

📚 目录

  1. 实用工具类型大全
  2. TypeScript 与 JSX
  3. 三斜杠指令与模块解析
  4. 类型兼容性与类型推断

1. 实用工具类型大全 🛠️

1.1 工具类型的设计理念

什么是实用工具类型?

TypeScript 的实用工具类型(Utility Types)是一套预定义的类型操作符,它们就像类型世界中的"瑞士军刀"。这些工具类型基于已有类型创建新类型,提供了强大的类型转换和操作能力。

工具类型解决的核心问题:

  1. 代码复用性:避免重复定义相似的类型结构
  2. 类型安全性:在类型层面进行安全的转换操作
  3. 开发效率:快速从现有类型派生出所需的新类型
  4. 维护性:当基础类型改变时,派生类型自动更新

工具类型的分类体系:

  • 属性选择类型:Pick、Omit - 选择或排除特定属性
  • 属性修饰类型:Partial、Required、Readonly - 修改属性的可选性和只读性
  • 记录构建类型:Record - 构建键值对类型
  • 函数相关类型:Parameters、ReturnType - 提取函数信息
  • 条件过滤类型:Exclude、Extract、NonNullable - 过滤联合类型
  • 字符串操作类型:Uppercase、Lowercase、Capitalize - 字符串字面量转换

1.2 属性选择工具类型:Pick 与 Omit

1.2.1 Pick<T, K> - 精确选择属性

Pick 工具类型允许我们从一个类型中选择特定的属性,创建一个新的类型。这在需要创建数据传输对象(DTO)或 API 响应类型时特别有用。

首先,让我们定义一个基础的用户数据模型:

// 定义完整的用户数据模型
interface CompleteUserProfile {
  id: number
  username: string
  email: string
  password: string
  phone: string
  age: number
  address: string
  createdAt: Date
  lastLogin: Date
  isActive: boolean
  role: 'admin' | 'user' | 'moderator'
  preferences: {
    theme: 'light' | 'dark'
    notifications: boolean
  }
}

Pick 的基本用法

Pick 工具类型的核心价值在于它能够从复杂的数据模型中提取出特定的字段组合:

// 创建公开展示的用户信息类型
type PublicUserInfo = Pick<CompleteUserProfile, 'id' | 'username' | 'createdAt' | 'role'>

// 使用示例
const userCard: PublicUserInfo = {
  id: 1,
  username: '张三',
  createdAt: new Date('2023-01-15'),
  role: 'user',
}

Pick 在 API 设计中的应用

在实际开发中,Pick 类型常用于创建不同场景下的数据类型:

// 用户注册时需要的信息
type UserRegistrationData = Pick<CompleteUserProfile, 'username' | 'email' | 'password' | 'phone'>

// 用户登录时需要的信息
type UserLoginData = Pick<CompleteUserProfile, 'email' | 'password'>

// 管理员查看的用户摘要信息
type AdminUserSummary = Pick<CompleteUserProfile, 'id' | 'username' | 'email' | 'isActive' | 'role' | 'lastLogin'>

这样的设计有几个优势:

  • 类型安全:确保只能使用指定的属性
  • 代码维护:当基础类型改变时,派生类型自动更新
  • API 清晰:明确定义每个接口需要的数据结构
1.2.2 Omit<T, K> - 精确排除属性

Omit 工具类型与 Pick 相反,它从类型中排除指定的属性。当我们需要基于现有类型创建去除某些敏感或不必要字段的新类型时,Omit 非常有用。

Omit 的基本用法

// 创建用户更新数据类型(排除不可修改的字段)
type UserUpdateData = Omit<CompleteUserProfile, 'id' | 'createdAt' | 'lastLogin'>

// 使用示例
const updateUserInfo = (userId: number, updateData: UserUpdateData): void => {
  console.log(`更新用户 ${userId} 的信息:`, updateData)
  // 实际的更新逻辑...
}

Omit 在数据库实体设计中的应用

Omit 在创建数据库实体类型时特别有用,可以排除自动生成的字段:

// 数据库中的用户表结构
interface UserEntity extends CompleteUserProfile {
  createdBy: number
  updatedAt: Date
  deletedAt: Date | null
}

// 创建新用户时的输入类型(排除自动生成的字段)
type CreateUserInput = Omit<UserEntity, 'id' | 'createdAt' | 'lastLogin' | 'createdBy' | 'updatedAt' | 'deletedAt'>

实际应用示例

const createUser = async (userData: CreateUserInput): Promise<UserEntity> => {
  // 添加自动生成的字段
  const newUser: UserEntity = {
    ...userData,
    id: Date.now(), // 实际项目中由数据库生成
    createdAt: new Date(),
    lastLogin: new Date(),
    createdBy: 1,
    updatedAt: new Date(),
    deletedAt: null,
  }
  
  return newUser
}

1.3 属性修饰工具类型:Partial、Required、Readonly

1.3.1 Partial - 可选化所有属性

Partial 工具类型将类型的所有属性变为可选属性。这在处理数据更新、配置对象或表单数据时非常有用。

定义用户配置类型

interface UserSettings {
  theme: 'light' | 'dark' | 'auto'
  language: 'zh' | 'en' | 'ja'
  notifications: {
    email: boolean
    push: boolean
    sms: boolean
  }
  privacy: {
    profileVisible: boolean
    showEmail: boolean
    showPhone: boolean
  }
}

Partial 的基本用法

使用 Partial,我们可以创建灵活的更新函数:

// 用户可以只更新部分设置
type UserSettingsUpdate = Partial<UserSettings>

const updateUserSettings = (userId: number, updates: UserSettingsUpdate): void => {
  console.log(`用户 ${userId} 更新设置:`, updates)
  // 只更新提供的字段,其他字段保持不变
}

实际使用示例

// 用户只想更改主题
updateUserSettings(1, { theme: 'dark' })

// 用户只想更改通知设置
updateUserSettings(2, {
  notifications: {
    email: false,
    push: true,
    sms: false,
  },
})

Partial 在表单处理中的应用

// 表单状态管理
interface FormState {
  values: Partial<UserSettings>
  errors: Partial<Record<keyof UserSettings, string>>
  touched: Partial<Record<keyof UserSettings, boolean>>
}

const initialFormState: FormState = {
  values: {},
  errors: {},
  touched: {},
}
1.3.2 Required - 必需化所有属性

Required 工具类型与 Partial 相反,它将所有可选属性变为必需属性。

定义应用配置类型

// 包含可选属性的配置接口
interface AppConfig {
  apiUrl?: string
  timeout?: number
  retryAttempts?: number
  enableLogging?: boolean
  debugMode?: boolean
}

Required 的基本用法

在应用初始化时,我们需要确保所有配置都有值:

// 完整的配置类型(所有属性都是必需的)
type CompleteAppConfig = Required<AppConfig>

const createDefaultConfig = (): CompleteAppConfig => {
  return {
    apiUrl: 'https://api.example.com',
    timeout: 5000,
    retryAttempts: 3,
    enableLogging: true,
    debugMode: false,
  }
}

实际应用示例

const initializeApp = (config: CompleteAppConfig): void => {
  console.log('应用初始化配置:', config)
  // 现在可以安全地访问所有配置属性,无需检查undefined
}

// 使用
const config = createDefaultConfig()
initializeApp(config)
1.3.3 Readonly - 只读化所有属性

Readonly 工具类型将所有属性设置为只读,防止意外修改。

定义应用常量

interface AppConstants {
  APP_NAME: string
  VERSION: string
  API_ENDPOINTS: {
    USERS: string
    PRODUCTS: string
    ORDERS: string
  }
  ERROR_CODES: {
    UNAUTHORIZED: number
    NOT_FOUND: number
    SERVER_ERROR: number
  }
}

Readonly 的基本用法

type ImmutableAppConstants = Readonly<AppConstants>

const APP_CONSTANTS: ImmutableAppConstants = {
  APP_NAME: 'MyApp',
  VERSION: '1.0.0',
  API_ENDPOINTS: {
    USERS: '/api/users',
    PRODUCTS: '/api/products',
    ORDERS: '/api/orders',
  },
  ERROR_CODES: {
    UNAUTHORIZED: 401,
    NOT_FOUND: 404,
    SERVER_ERROR: 500,
  },
}

类型安全保障

// 尝试修改会导致编译错误
// APP_CONSTANTS.VERSION = '2.0.0'; // ❌ Error: Cannot assign to 'VERSION' because it is a read-only property

1.4 记录构建工具类型:Record

1.4.1 Record<K, T> - 构建键值对类型

Record 工具类型用于创建一个对象类型,其中键的类型为 K,值的类型为 T。这在创建映射表、配置对象或索引结构时非常有用。

多语言文本映射

// 定义支持的语言类型
type SupportedLanguage = 'zh' | 'en' | 'ja' | 'ko'

// 创建多语言文本映射
type LanguageTexts = Record<SupportedLanguage, string>

const welcomeMessage: LanguageTexts = {
  zh: '欢迎使用我们的应用',
  en: 'Welcome to our application',
  ja: 'アプリケーションへようこそ',
  ko: '애플리케이션에 오신 것을 환영합니다',
}

用户权限配置

Record 在创建配置映射时特别有用:

// 用户角色和权限定义
type UserRole = 'admin' | 'moderator' | 'user' | 'guest'

interface Permission {
  read: boolean
  write: boolean
  delete: boolean
  manage: boolean
}

// 为每个角色定义权限
type RolePermissions = Record<UserRole, Permission>

权限配置实现

const rolePermissions: RolePermissions = {
  admin: {
    read: true,
    write: true,
    delete: true,
    manage: true,
  },
  moderator: {
    read: true,
    write: true,
    delete: false,
    manage: false,
  },
  user: {
    read: true,
    write: false,
    delete: false,
    manage: false,
  },
  guest: {
    read: true,
    write: false,
    delete: false,
    manage: false,
  },
}

HTTP 状态码映射

Record 还可以用于创建动态的对象结构:

// HTTP状态码定义
type HttpStatusCode = 200 | 201 | 400 | 401 | 403 | 404 | 500

interface StatusInfo {
  message: string
  description: string
  category: 'success' | 'client_error' | 'server_error'
}

type HttpStatusMap = Record<HttpStatusCode, StatusInfo>

状态码映射实现

const httpStatusMap: HttpStatusMap = {
  200: {
    message: 'OK',
    description: '请求成功',
    category: 'success',
  },
  201: {
    message: 'Created',
    description: '资源创建成功',
    category: 'success',
  },
  400: {
    message: 'Bad Request',
    description: '请求参数错误',
    category: 'client_error',
  },
  401: {
    message: 'Unauthorized',
    description: '未授权访问',
    category: 'client_error',
  },
  403: {
    message: 'Forbidden',
    description: '禁止访问',
    category: 'client_error',
  },
  404: {
    message: 'Not Found',
    description: '资源未找到',
    category: 'client_error',
  },
  500: {
    message: 'Internal Server Error',
    description: '服务器内部错误',
    category: 'server_error',
  },
}

1.5 函数相关工具类型:Parameters、ReturnType、ConstructorParameters

1.5.1 Parameters - 提取函数参数类型

Parameters 工具类型能够提取函数的参数类型,返回一个元组类型。这在需要复用函数参数类型或创建高阶函数时特别有用。

定义复杂函数

// 定义一个复杂的订单创建函数
function createOrder(
  orderId: string,
  productList: string[],
  totalAmount: number,
  customerInfo: {
    name: string
    email: string
    phone: string
  },
  note?: string,
): Promise<{ success: boolean; orderId: string }> {
  console.log(`创建订单 ${orderId}`)
  // 实际的订单创建逻辑...
  return Promise.resolve({ success: true, orderId })
}

提取参数类型

使用 Parameters 提取参数类型,可以在其他地方复用这些类型定义:

// 提取createOrder函数的参数类型
type CreateOrderParams = Parameters<typeof createOrder>
// [string, string[], number, { name: string; email: string; phone: string; }, string?]

// 创建批量订单处理函数
async function batchCreateOrder(orders: CreateOrderParams[]): Promise<void> {
  for (const orderParams of orders) {
    try {
      await createOrder(...orderParams)
      console.log('订单创建成功')
    } catch (error) {
      console.error('订单创建失败:', error)
    }
  }
}

函数装饰器应用

Parameters 在创建函数装饰器或中间件时也很有用:

// 创建日志装饰器
function logFunction<T extends (...args: any[]) => any>(
  originalFunction: T
): (...args: Parameters<T>) => ReturnType<T> {
  return (...args: Parameters<T>) => {
    console.log(`调用函数 ${originalFunction.name},参数:`, args)
    const result = originalFunction(...args)
    console.log(`函数 ${originalFunction.name} 返回:`, result)
    return result
  }
}

// 使用装饰器
const loggedCreateOrder = logFunction(createOrder)
1.5.2 ReturnType - 提取函数返回类型

ReturnType 工具类型提取函数的返回类型。这在需要基于函数返回值创建新类型或进行类型推导时非常有用。

定义数据统计函数

// 定义一个数据统计函数
function calculateStatistics(data: number[]) {
  const sorted = [...data].sort((a, b) => a - b)
  const sum = data.reduce((a, b) => a + b, 0)

  return {
    count: data.length,
    sum: sum,
    average: sum / data.length,
    median: sorted[Math.floor(sorted.length / 2)],
    min: Math.min(...data),
    max: Math.max(...data),
    range: Math.max(...data) - Math.min(...data),
  }
}

提取返回类型

使用 ReturnType 可以轻松获取返回值类型:

// 提取统计结果类型
type StatisticsResult = ReturnType<typeof calculateStatistics>
// { count: number; sum: number; average: number; median: number; min: number; max: number; range: number }

// 在其他地方使用这个类型
interface DataAnalysis {
  rawData: number[]
  statistics: StatisticsResult
  timestamp: Date
}

const performAnalysis = (data: number[]): DataAnalysis => {
  return {
    rawData: data,
    statistics: calculateStatistics(data),
    timestamp: new Date(),
  }
}

API 响应类型定义

ReturnType 在 API 响应类型定义中也很有用:

// API函数定义
async function fetchUserProfile(userId: string) {
  // 模拟API调用
  return {
    user: {
      id: userId,
      name: '张三',
      email: 'zhangsan@example.com',
    },
    metadata: {
      lastUpdated: new Date(),
      version: 1,
    },
  }
}

// 提取API响应类型
type UserProfileResponse = ReturnType<typeof fetchUserProfile>
// Promise<{ user: { id: string; name: string; email: string; }; metadata: { lastUpdated: Date; version: number; }; }>

// 获取实际的数据类型(去除Promise包装)
type UserProfileData = Awaited<ReturnType<typeof fetchUserProfile>>
// { user: { id: string; name: string; email: string; }; metadata: { lastUpdated: Date; version: number; }; }
1.5.3 ConstructorParameters - 提取构造函数参数类型

ConstructorParameters 工具类型提取类构造函数的参数类型。这在创建工厂函数或需要动态实例化类时特别有用。

定义产品管理类

// 定义一个复杂的产品管理类
class ProductManager {
  constructor(
    private productName: string,
    private price: number,
    private category: 'electronics' | 'clothing' | 'books' | 'food',
    private stockQuantity: number = 0,
    private supplier?: {
      name: string
      contact: string
    },
  ) {
    this.validateProduct()
  }

  private validateProduct(): void {
    if (this.price <= 0) {
      throw new Error('产品价格必须大于0')
    }
    if (this.stockQuantity < 0) {
      throw new Error('库存数量不能为负数')
    }
  }

  getProductInfo() {
    return {
      name: this.productName,
      price: this.price,
      category: this.category,
      stock: this.stockQuantity,
      supplier: this.supplier,
    }
  }
}

提取构造函数参数类型

// 提取构造函数参数类型
type ProductManagerParams = ConstructorParameters<typeof ProductManager>
// [string, number, 'electronics' | 'clothing' | 'books' | 'food', number?, { name: string; contact: string; }?]

// 创建产品工厂函数
function createProductManager(...params: ProductManagerParams): ProductManager {
  try {
    return new ProductManager(...params)
  } catch (error) {
    console.error('创建产品管理器失败:', error)
    throw error
  }
}

// 批量创建产品
function batchCreateProducts(productsData: ProductManagerParams[]): ProductManager[] {
  return productsData.map(params => createProductManager(...params))
}

通用对象工厂

ConstructorParameters 在创建对象池或缓存系统时也很有用:

// 创建一个通用的对象工厂
class ObjectFactory<T> {
  private instances = new Map<string, T>()

  constructor(private ClassConstructor: new (...args: any[]) => T) {}

  create(key: string, ...args: ConstructorParameters<new (...args: any[]) => T>): T {
    if (this.instances.has(key)) {
      return this.instances.get(key)!
    }

    const instance = new this.ClassConstructor(...args)
    this.instances.set(key, instance)
    return instance
  }

  get(key: string): T | undefined {
    return this.instances.get(key)
  }
}

// 使用对象工厂
const productFactory = new ObjectFactory(ProductManager)
const product1 = productFactory.create('laptop', 'MacBook Pro', 15999, 'electronics', 10)
const product2 = productFactory.create('shirt', 'Cotton T-Shirt', 99, 'clothing', 50)

1.6 条件过滤工具类型:Exclude、Extract、NonNullable

1.6.1 Exclude<T, U> - 排除联合类型成员

Exclude 工具类型从联合类型 T 中排除可以赋值给 U 的类型。这在需要过滤掉特定类型或创建更精确的联合类型时非常有用。

请求状态管理

// 定义一个包含多种状态的联合类型
type RequestStatus = 'idle' | 'loading' | 'success' | 'error' | 'cancelled'

// 排除终态状态,只保留进行中的状态
type ActiveStatus = Exclude<RequestStatus, 'success' | 'error' | 'cancelled'>
// 'idle' | 'loading'

// 排除初始状态,只保留已开始的状态
type StartedStatus = Exclude<RequestStatus, 'idle'>
// 'loading' | 'success' | 'error' | 'cancelled'

事件类型过滤

Exclude 在处理事件类型时特别有用:

// 定义所有可能的事件类型
type AllEventTypes = 'click' | 'hover' | 'focus' | 'blur' | 'keydown' | 'keyup' | 'scroll' | 'resize'

// 排除键盘事件,只保留鼠标和窗口事件
type NonKeyboardEvents = Exclude<AllEventTypes, 'keydown' | 'keyup'>
// 'click' | 'hover' | 'focus' | 'blur' | 'scroll' | 'resize'

// 排除窗口事件,只保留元素事件
type ElementEvents = Exclude<AllEventTypes, 'scroll' | 'resize'>
// 'click' | 'hover' | 'focus' | 'blur' | 'keydown' | 'keyup'

创建事件处理器映射

// 创建事件处理器映射
type EventHandlerMap = {
  [K in NonKeyboardEvents]: (event: Event) => void
}

const eventHandlers: EventHandlerMap = {
  click: event => console.log('点击事件'),
  hover: event => console.log('悬停事件'),
  focus: event => console.log('聚焦事件'),
  blur: event => console.log('失焦事件'),
  scroll: event => console.log('滚动事件'),
  resize: event => console.log('窗口大小改变事件'),
}
1.6.2 Extract<T, U> - 提取联合类型成员

Extract 工具类型与 Exclude 相反,它从联合类型 T 中提取可以赋值给 U 的类型。

数据类型分类

// 定义数据类型的联合类型
type DataType = string | number | boolean | Date | null | undefined

// 提取原始类型
type PrimitiveTypes = Extract<DataType, string | number | boolean>
// string | number | boolean

// 提取对象类型
type ObjectTypes = Extract<DataType, object>
// Date (因为Date是object类型)

// 提取可空类型
type NullableTypes = Extract<DataType, null | undefined>
// null | undefined

API 响应类型处理

Extract 在 API 响应类型处理中很有用:

// 定义API响应的不同状态
type ApiResponse<T> = 
  | { status: 'loading'; data: null } 
  | { status: 'success'; data: T } 
  | { status: 'error'; error: string; data: null }

// 提取成功响应类型
type SuccessResponse<T> = Extract<ApiResponse<T>, { status: 'success' }>
// { status: 'success'; data: T }

// 提取错误响应类型
type ErrorResponse = Extract<ApiResponse<any>, { status: 'error' }>
// { status: 'error'; error: string; data: null }

使用提取的类型

// 使用提取的类型
function handleSuccessResponse<T>(response: SuccessResponse<T>): T {
  return response.data // TypeScript知道这里data不会是null
}

function handleErrorResponse(response: ErrorResponse): string {
  return response.error // TypeScript知道这里有error属性
}
1.6.3 NonNullable - 排除 null 和 undefined

NonNullable 工具类型从类型 T 中排除 null 和 undefined,这在处理可能为空的值时非常有用。

处理可空类型

// 定义一个可能包含空值的类型
type MaybeUser = {
  id: number
  name: string
  email: string | null
  phone?: string
  avatar: string | undefined
}

// 排除null和undefined,获取确定存在的类型
type DefiniteString = NonNullable<string | null | undefined> // string
type DefiniteEmail = NonNullable<MaybeUser['email']> // string
type DefiniteAvatar = NonNullable<MaybeUser['avatar']> // string

数据清洗应用

NonNullable 在数据处理管道中特别有用:

// 数据清洗函数
function cleanUserData(users: MaybeUser[]): Array<{
  id: number
  name: string
  email: NonNullable<MaybeUser['email']>
  phone: NonNullable<MaybeUser['phone']>
  avatar: NonNullable<MaybeUser['avatar']>
}> {
  return users
    .filter((user): user is MaybeUser & {
      email: NonNullable<MaybeUser['email']>
      phone: NonNullable<MaybeUser['phone']>
      avatar: NonNullable<MaybeUser['avatar']>
    } => {
      return user.email !== null && user.phone !== undefined && user.avatar !== undefined
    })
    .map(user => ({
      id: user.id,
      name: user.name,
      email: user.email,
      phone: user.phone,
      avatar: user.avatar,
    }))
}

1.7 字符串操作工具类型:Uppercase、Lowercase、Capitalize、Uncapitalize

1.7.1 基础字符串转换

TypeScript 提供了四个内置的字符串操作工具类型,用于转换字符串字面量类型的大小写:

// 基础字符串转换
type UppercaseExample = Uppercase<'hello world'> // 'HELLO WORLD'
type LowercaseExample = Lowercase<'HELLO WORLD'> // 'hello world'
type CapitalizeExample = Capitalize<'hello world'> // 'Hello world'
type UncapitalizeExample = Uncapitalize<'Hello World'> // 'hello World'
1.7.2 HTTP 方法和 API 端点类型

这些工具类型在创建 API 端点类型或常量定义时特别有用:

// HTTP方法定义
type HttpMethod = 'get' | 'post' | 'put' | 'delete' | 'patch'
type UppercaseHttpMethod = Uppercase<HttpMethod>
// 'GET' | 'POST' | 'PUT' | 'DELETE' | 'PATCH'

// 创建API端点类型
type ApiEndpoint = 'users' | 'products' | 'orders' | 'categories'

// 生成不同格式的端点名称
type CapitalizedEndpoint = Capitalize<ApiEndpoint>
// 'Users' | 'Products' | 'Orders' | 'Categories'

type UppercaseEndpoint = Uppercase<ApiEndpoint>
// 'USERS' | 'PRODUCTS' | 'ORDERS' | 'CATEGORIES'
1.7.3 结合模板字面量类型
// 结合模板字面量类型使用
type ApiUrl<T extends ApiEndpoint> = `/api/${T}`
type ApiConstant<T extends ApiEndpoint> = `API_${Uppercase<T>}_ENDPOINT`

type UserApiUrl = ApiUrl<'users'> // '/api/users'
type UserApiConstant = ApiConstant<'users'> // 'API_USERS_ENDPOINT'
1.7.4 环境变量配置

在实际应用中,这些工具类型常用于配置对象和常量定义:

// 环境变量配置
type Environment = 'development' | 'staging' | 'production'

// 生成配置键名
type ConfigKey<T extends Environment> = `${Uppercase<T>}_CONFIG`
type DatabaseUrl<T extends Environment> = `${Uppercase<T>}_DATABASE_URL`

type DevConfigKey = ConfigKey<'development'> // 'DEVELOPMENT_CONFIG'
type ProdDatabaseUrl = DatabaseUrl<'production'> // 'PRODUCTION_DATABASE_URL'

创建配置对象类型

// 创建配置对象类型
type EnvironmentConfig = {
  [K in Environment as ConfigKey<K>]: {
    apiUrl: string
    databaseUrl: string
    logLevel: 'debug' | 'info' | 'warn' | 'error'
  }
}

const config: EnvironmentConfig = {
  DEVELOPMENT_CONFIG: {
    apiUrl: 'http://localhost:3000',
    databaseUrl: 'mongodb://localhost:27017/dev',
    logLevel: 'debug',
  },
  STAGING_CONFIG: {
    apiUrl: 'https://staging-api.example.com',
    databaseUrl: 'mongodb://staging-db.example.com:27017/staging',
    logLevel: 'info',
  },
  PRODUCTION_CONFIG: {
    apiUrl: 'https://api.example.com',
    databaseUrl: 'mongodb://prod-db.example.com:27017/prod',
    logLevel: 'warn',
  },
}

1.8 高级组合工具类型应用

1.8.1 组合多个工具类型解决复杂业务需求

在实际开发中,我们经常需要组合多个工具类型来创建复杂的类型转换。以下是一个完整的用户数据管理系统的类型设计。

基础用户数据模型

// 基础用户数据模型
interface BaseUserData {
  readonly id: number
  username: string
  email: string
  password: string
  firstName: string
  lastName: string
  dateOfBirth: Date
  createdAt: Date
  updatedAt: Date
  lastLogin?: Date
  isActive: boolean
  role: 'admin' | 'moderator' | 'user'
  profile?: {
    avatar?: string
    bio?: string
    website?: string
    location?: string
    preferences: {
      theme: 'light' | 'dark' | 'auto'
      language: 'zh' | 'en' | 'ja'
      notifications: {
        email: boolean
        push: boolean
        sms: boolean
      }
    }
  }
}
1.8.2 专用类型创建

基于基础模型,我们可以创建各种专用的类型:

用户注册类型

// 用户注册输入类型:排除自动生成的字段,密码必需,profile可选
type UserRegistrationInput = Omit<BaseUserData, 'id' | 'createdAt' | 'updatedAt' | 'lastLogin'> & {
  password: string // 确保密码是必需的
  confirmPassword: string // 添加确认密码字段
}

用户更新类型

// 用户更新输入类型:只能更新特定字段,且都是可选的
type UserUpdateInput = Partial<Pick<BaseUserData, 'firstName' | 'lastName' | 'dateOfBirth' | 'profile'>> & {
  // 密码更新需要特殊处理
  passwordUpdate?: {
    currentPassword: string
    newPassword: string
    confirmPassword: string
  }
}

公开用户信息类型

// 公开用户信息类型:排除敏感信息
type PublicUserInfo = Readonly<Omit<BaseUserData, 'password' | 'email' | 'lastLogin' | 'updatedAt'>> & {
  // 添加计算属性
  fullName: string
  memberSince: string
}

管理员视图类型

// 管理员查看的用户信息:包含更多字段但排除密码
type AdminUserView = Readonly<Omit<BaseUserData, 'password'>> & {
  // 添加管理员相关的计算属性
  accountAge: number // 账户年龄(天数)
  loginFrequency: 'daily' | 'weekly' | 'monthly' | 'rarely'
}
1.8.3 API 响应类型设计
// API响应类型:结合条件类型和工具类型
type ApiResponse<T> = 
  | { status: 'success'; data: T; timestamp: Date } 
  | { status: 'error'; error: string; code: number; timestamp: Date }

type UserApiResponse = ApiResponse<PublicUserInfo>
type UsersListApiResponse = ApiResponse<PublicUserInfo[]>
type AdminUserApiResponse = ApiResponse<AdminUserView>
1.8.4 类型安全的服务接口
// 用户服务类型定义
interface UserService {
  // 注册用户
  register(input: UserRegistrationInput): Promise<Extract<UserApiResponse, { status: 'success' }>>

  // 更新用户信息
  updateUser(userId: number, input: UserUpdateInput): Promise<UserApiResponse>

  // 获取公开用户信息
  getPublicProfile(userId: number): Promise<UserApiResponse>

  // 获取用户列表(管理员权限)
  getUsersList(filters?: Partial<Pick<BaseUserData, 'role' | 'isActive'>>): Promise<UsersListApiResponse>

  // 获取用户详情(管理员权限)
  getAdminUserView(userId: number): Promise<AdminUserApiResponse>
}
1.8.5 服务实现示例
// 实现用户服务
class UserServiceImpl implements UserService {
  async register(input: UserRegistrationInput): Promise<Extract<UserApiResponse, { status: 'success' }>> {
    // 验证输入
    if (input.password !== input.confirmPassword) {
      throw new Error('密码不匹配')
    }

    // 创建用户逻辑...
    const newUser: PublicUserInfo = {
      id: Date.now(),
      username: input.username,
      firstName: input.firstName,
      lastName: input.lastName,
      dateOfBirth: input.dateOfBirth,
      createdAt: new Date(),
      isActive: true,
      role: input.role,
      profile: input.profile,
      fullName: `${input.firstName} ${input.lastName}`,
      memberSince: new Date().toISOString(),
    }

    return {
      status: 'success',
      data: newUser,
      timestamp: new Date(),
    }
  }

  async updateUser(userId: number, input: UserUpdateInput): Promise<UserApiResponse> {
    // 更新用户逻辑...
    console.log('更新用户:', userId, input)
    throw new Error('Method not implemented.')
  }

  async getPublicProfile(userId: number): Promise<UserApiResponse> {
    // 获取公开信息逻辑...
    throw new Error('Method not implemented.')
  }

  async getUsersList(filters?: Partial<Pick<BaseUserData, 'role' | 'isActive'>>): Promise<UsersListApiResponse> {
    // 获取用户列表逻辑...
    throw new Error('Method not implemented.')
  }

  async getAdminUserView(userId: number): Promise<AdminUserApiResponse> {
    // 获取管理员视图逻辑...
    throw new Error('Method not implemented.')
  }
}

通过这种方式,我们成功地将复杂的业务需求转化为类型安全的代码结构,每个类型都有明确的用途和约束,大大提高了代码的可维护性和安全性。


2. TypeScript 与 JSX 🎭

2.1 JSX 的设计理念与 TypeScript 集成

什么是 JSX?为什么 TypeScript 要支持 JSX?

JSX(JavaScript XML)是一种可嵌入的类 XML 语法扩展,最初由 React 框架引入,后来被其他框架采用。JSX 允许开发者在 JavaScript 代码中直接编写类似 HTML 的标记,使得 UI 组件的编写更加直观和声明式。

JSX 解决的核心问题:

  1. 视图与逻辑的分离:传统的模板语言往往功能有限,JSX 允许在模板中使用完整的 JavaScript 表达式
  2. 类型安全的 UI 开发:TypeScript 的 JSX 支持提供了编译时的类型检查
  3. 组件化开发:JSX 天然支持组件的嵌套和组合
  4. 开发体验优化:IDE 可以提供智能提示、重构等功能

TypeScript JSX 的特殊优势:

TypeScript 对 JSX 的支持不仅仅是语法转换,更重要的是提供了类型安全保障:

  • 组件 props 类型检查:确保传递给组件的属性类型正确
  • 事件处理器类型安全:自动推断事件处理函数的参数类型
  • 子元素类型验证:检查组件是否接受特定类型的子元素
  • 泛型组件支持:支持创建可复用的泛型组件

2.2 JSX 编译配置详解

TypeScript JSX 编译选项

TypeScript 提供了多种 JSX 编译模式,每种模式适用于不同的项目需求和构建环境:

// tsconfig.json - 完整的JSX配置
{
  "compilerOptions": {
    "jsx": "react-jsx", // JSX编译模式
    "jsxImportSource": "react", // JSX运行时导入源
    "strict": true, // 启用严格类型检查
    "esModuleInterop": true, // 启用ES模块互操作
    "allowSyntheticDefaultImports": true, // 允许合成默认导入
    "moduleResolution": "node", // 模块解析策略
    "lib": ["dom", "dom.iterable", "es6"], // 包含的库文件
    "skipLibCheck": true, // 跳过库文件类型检查
    "declaration": true, // 生成类型声明文件
    "declarationMap": true // 生成声明文件映射
  },
  "include": [
    "src/**/*.tsx", // 包含所有TSX文件
    "src/**/*.ts" // 包含所有TS文件
  ],
  "exclude": [
    "node_modules", // 排除依赖包
    "dist", // 排除构建输出
    "**/*.test.tsx" // 排除测试文件(可选)
  ]
}

JSX 编译模式详细对比

不同的 JSX 编译模式会产生不同的 JavaScript 输出,了解这些差异有助于选择合适的配置:

编译模式 输入代码 输出代码 适用场景 优缺点
preserve <div /> <div /> 需要后续工具处理 保持原始 JSX 语法,适合多步构建
react <div /> React.createElement("div") React 16 及以前版本 需要导入 React,兼容性好
react-jsx <div /> _jsx("div", {}) React 17+ 自动导入 JSX 运行时,包体积更小
react-jsxdev <div /> _jsxDEV("div", {}) 开发环境 包含调试信息,便于开发调试
react-native <div /> <div /> React Native 保持 JSX 语法,由 RN 处理

JSX 运行时配置

React 17 引入了新的 JSX 转换,不再需要在每个文件中导入 React:

// 旧的JSX转换(jsx: "react")
import React from 'react' // 必须导入

const OldComponent = () => {
  return <div>Hello World</div> // 转换为 React.createElement("div", null, "Hello World")
}

// 新的JSX转换(jsx: "react-jsx")
// 无需导入React
const NewComponent = () => {
  return <div>Hello World</div> // 转换为 _jsx("div", { children: "Hello World" })
}

2.3 JSX 类型系统基础

2.3.1 组件 Props 类型定义

在 TypeScript 中,为 JSX 组件定义准确的 Props 类型是确保类型安全的关键。

基础 Props 接口定义

// 基础Props接口定义
interface UserCardProps {
  // 必需属性
  userId: number
  userName: string
  userEmail: string

  // 可选属性
  avatar?: string
  isOnline?: boolean

  // 联合类型属性
  role: 'admin' | 'moderator' | 'user'
  status: 'active' | 'inactive' | 'pending'

  // 函数属性
  onUserClick: (userId: number) => void
  onStatusChange?: (newStatus: string) => void

  // 复杂对象属性
  preferences: {
    theme: 'light' | 'dark'
    notifications: boolean
  }

  // 子元素(可选)
  children?: React.ReactNode
}
2.3.2 函数组件的类型注解方式

TypeScript 提供了多种方式来为函数组件添加类型注解:

方式1:使用 React.FC 类型

// 使用React.FC(函数组件)类型
const UserCard: React.FC<UserCardProps> = ({
  userId,
  userName,
  userEmail,
  avatar,
  isOnline = false,
  role,
  status,
  onUserClick,
  onStatusChange,
  preferences,
  children,
}) => {
  return (
    <div className={`user-card ${role}`}>
      <div className="user-info">
        {avatar && <img src={avatar} alt={`${userName}头像`} />}
        <h3>{userName}</h3>
        <p>{userEmail}</p>
        <span className={`status ${status}`}>
          {status === 'active' ? '活跃' : status === 'inactive' ? '离线' : '待审核'}
        </span>
        {isOnline && <span className="online-indicator">在线</span>}
      </div>

      <div className="user-actions">
        <button onClick={() => onUserClick(userId)}>查看详情</button>
        {onStatusChange && (
          <select value={status} onChange={e => onStatusChange(e.target.value)}>
            <option value="active">活跃</option>
            <option value="inactive">离线</option>
            <option value="pending">待审核</option>
          </select>
        )}
      </div>

      {children && <div className="user-extra">{children}</div>}
    </div>
  )
}

方式2:直接函数声明(推荐)

function UserProfile(props: UserCardProps): JSX.Element {
  // 解构props
  const { userId, userName, userEmail, role, onUserClick } = props

  return (
    <div className="user-profile">
      <h2>{userName}</h2>
      <p>邮箱: {userEmail}</p>
      <p>角色: {role}</p>
      <button onClick={() => onUserClick(userId)}>编辑用户</button>
    </div>
  )
}

方式3:箭头函数with类型注解

const UserSummary = (props: UserCardProps): React.ReactElement => {
  return (
    <div className="user-summary">
      <span>{props.userName}</span>
      <span className={`role-badge ${props.role}`}>{props.role}</span>
    </div>
  )
}
2.3.3 类组件的类型定义

类组件需要定义 Props 和 State 的类型。

Props 和 State 类型定义

// 类组件Props和State类型定义
interface UserManagerProps {
  initialUsers: UserCardProps[]
  maxUsers: number
  onUserLimitReached: () => void
}

interface UserManagerState {
  users: UserCardProps[]
  selectedUserId: number | null
  isLoading: boolean
  error: string | null
  filterRole: 'all' | 'admin' | 'moderator' | 'user'
}

类组件基本结构

class UserManager extends React.Component<UserManagerProps, UserManagerState> {
  constructor(props: UserManagerProps) {
    super(props)

    // 初始化state
    this.state = {
      users: props.initialUsers,
      selectedUserId: null,
      isLoading: false,
      error: null,
      filterRole: 'all',
    }
  }

  // 生命周期方法
  componentDidMount(): void {
    console.log('UserManager组件已挂载')
  }

  componentDidUpdate(prevProps: UserManagerProps, prevState: UserManagerState): void {
    if (prevState.users.length !== this.state.users.length) {
      if (this.state.users.length >= this.props.maxUsers) {
        this.props.onUserLimitReached()
      }
    }
  }
}

事件处理方法

// 在UserManager类中添加事件处理方法
handleUserClick = (userId: number): void => {
  this.setState({ selectedUserId: userId })
}

handleStatusChange = (userId: number, newStatus: string): void => {
  this.setState(prevState => ({
    users: prevState.users.map(user => 
      user.userId === userId 
        ? { ...user, status: newStatus as 'active' | 'inactive' | 'pending' } 
        : user
    ),
  }))
}

handleFilterChange = (role: UserManagerState['filterRole']): void => {
  this.setState({ filterRole: role })
}

计算属性和渲染方法

// 计算属性
get filteredUsers(): UserCardProps[] {
  if (this.state.filterRole === 'all') {
    return this.state.users
  }
  return this.state.users.filter(user => user.role === this.state.filterRole)
}

// 渲染方法
render(): React.ReactNode {
  const { isLoading, error, selectedUserId, filterRole } = this.state
  const filteredUsers = this.filteredUsers

  if (isLoading) {
    return <div className="loading">加载中...</div>
  }

  if (error) {
    return <div className="error">错误: {error}</div>
  }

  return (
    <div className="user-manager">
      <div className="user-controls">
        <h2>用户管理 ({filteredUsers.length})</h2>
        <select value={filterRole} onChange={e => this.handleFilterChange(e.target.value as any)}>
          <option value="all">所有角色</option>
          <option value="admin">管理员</option>
          <option value="moderator">版主</option>
          <option value="user">普通用户</option>
        </select>
      </div>

      <div className="user-list">
        {filteredUsers.map(user => (
          <UserCard
            key={user.userId}
            {...user}
            onUserClick={this.handleUserClick}
            onStatusChange={newStatus => this.handleStatusChange(user.userId, newStatus)}
          />
        ))}
      </div>

      {selectedUserId && <div className="selected-user">已选择用户ID: {selectedUserId}</div>}
    </div>
  )
}

2.4 JSX 高级特性:泛型组件与事件处理

2.4.1 泛型组件的设计与实现

泛型组件是 TypeScript JSX 的强大特性,允许我们创建可复用的、类型安全的组件。

泛型列表组件接口定义

// 泛型列表组件
interface GenericListProps<T> {
  items: T[]
  renderItem: (item: T, index: number) => React.ReactNode
  keyExtractor: (item: T) => string | number
  emptyMessage?: string
  className?: string
}

泛型组件实现

function GenericList<T>({ 
  items, 
  renderItem, 
  keyExtractor, 
  emptyMessage = '暂无数据', 
  className = 'generic-list' 
}: GenericListProps<T>): React.ReactElement {
  if (items.length === 0) {
    return <div className={`${className} empty`}>{emptyMessage}</div>
  }

  return (
    <div className={className}>
      {items.map((item, index) => (
        <div key={keyExtractor(item)} className="list-item">
          {renderItem(item, index)}
        </div>
      ))}
    </div>
  )
}

泛型组件使用示例

// 定义产品类型
interface Product {
  id: number
  name: string
  price: number
  category: string
}

const ProductList: React.FC<{ products: Product[] }> = ({ products }) => {
  return (
    <GenericList<Product>
      items={products}
      keyExtractor={product => product.id}
      renderItem={(product, index) => (
        <div className="product-item">
          <h3>{product.name}</h3>
          <p>价格: ¥{product.price}</p>
          <p>分类: {product.category}</p>
          <span className="index">#{index + 1}</span>
        </div>
      )}
      emptyMessage="暂无产品"
      className="product-list"
    />
  )
}

高阶组件(HOC)的类型定义

高阶组件是 React 中的重要模式,TypeScript 为其提供了强大的类型支持:

// 定义HOC的类型
type WithLoadingProps = {
  isLoading: boolean
}

// 高阶组件:添加加载状态
function withLoading<P extends object>(WrappedComponent: React.ComponentType<P>): React.ComponentType<P & WithLoadingProps> {
  return function WithLoadingComponent(props: P & WithLoadingProps) {
    const { isLoading, ...restProps } = props

    if (isLoading) {
      return (
        <div className="loading-wrapper">
          <div className="spinner">加载中...</div>
        </div>
      )
    }

    return <WrappedComponent {...(restProps as P)} />
  }
}

// 使用HOC
interface UserListProps {
  users: UserCardProps[]
  onUserSelect: (userId: number) => void
}

const UserList: React.FC<UserListProps> = ({ users, onUserSelect }) => {
  return (
    <div className="user-list">
      {users.map(user => (
        <UserCard key={user.userId} {...user} onUserClick={onUserSelect} />
      ))}
    </div>
  )
}

// 应用HOC
const UserListWithLoading = withLoading(UserList)

// 使用增强后的组件
const App: React.FC = () => {
  const [users, setUsers] = React.useState<UserCardProps[]>([])
  const [isLoading, setIsLoading] = React.useState(true)

  return <UserListWithLoading users={users} onUserSelect={userId => console.log('选择用户:', userId)} isLoading={isLoading} />
}

事件处理的类型安全

TypeScript 为 React 事件提供了详细的类型定义,确保事件处理的类型安全:

// 表单事件处理
interface FormData {
  username: string
  email: string
  password: string
  confirmPassword: string
  agreeToTerms: boolean
}

const RegistrationForm: React.FC = () => {
  const [formData, setFormData] = React.useState<FormData>({
    username: '',
    email: '',
    password: '',
    confirmPassword: '',
    agreeToTerms: false,
  })

  const [errors, setErrors] = React.useState<Partial<FormData>>({})

  // 输入框变化处理
  const handleInputChange = (event: React.ChangeEvent<HTMLInputElement>): void => {
    const { name, value, type, checked } = event.target

    setFormData(prev => ({
      ...prev,
      [name]: type === 'checkbox' ? checked : value,
    }))

    // 清除相关错误
    if (errors[name as keyof FormData]) {
      setErrors(prev => ({ ...prev, [name]: undefined }))
    }
  }

  // 表单提交处理
  const handleSubmit = (event: React.FormEvent<HTMLFormElement>): void => {
    event.preventDefault()

    // 表单验证
    const newErrors: Partial<FormData> = {}

    if (!formData.username.trim()) {
      newErrors.username = '用户名不能为空'
    }

    if (!formData.email.includes('@')) {
      newErrors.email = '请输入有效的邮箱地址'
    }

    if (formData.password.length < 6) {
      newErrors.password = '密码至少6个字符'
    }

    if (formData.password !== formData.confirmPassword) {
      newErrors.confirmPassword = '两次密码输入不一致'
    }

    if (!formData.agreeToTerms) {
      newErrors.agreeToTerms = '请同意服务条款'
    }

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors)
      return
    }

    // 提交表单
    console.log('提交表单:', formData)
  }

  // 键盘事件处理
  const handleKeyDown = (event: React.KeyboardEvent<HTMLInputElement>): void => {
    if (event.key === 'Enter' && event.ctrlKey) {
      // Ctrl+Enter 快速提交
      const form = event.currentTarget.form
      if (form) {
        form.requestSubmit()
      }
    }
  }

  // 焦点事件处理
  const handleFocus = (event: React.FocusEvent<HTMLInputElement>): void => {
    const { name } = event.target
    console.log(`字段 ${name} 获得焦点`)
  }

  const handleBlur = (event: React.FocusEvent<HTMLInputElement>): void => {
    const { name, value } = event.target
    console.log(`字段 ${name} 失去焦点,值为: ${value}`)
  }

  return (
    <form onSubmit={handleSubmit} className="registration-form">
      <div className="form-group">
        <label htmlFor="username">用户名:</label>
        <input
          type="text"
          id="username"
          name="username"
          value={formData.username}
          onChange={handleInputChange}
          onKeyDown={handleKeyDown}
          onFocus={handleFocus}
          onBlur={handleBlur}
          className={errors.username ? 'error' : ''}
        />
        {errors.username && <span className="error-message">{errors.username}</span>}
      </div>

      <div className="form-group">
        <label htmlFor="email">邮箱:</label>
        <input
          type="email"
          id="email"
          name="email"
          value={formData.email}
          onChange={handleInputChange}
          onKeyDown={handleKeyDown}
          onFocus={handleFocus}
          onBlur={handleBlur}
          className={errors.email ? 'error' : ''}
        />
        {errors.email && <span className="error-message">{errors.email}</span>}
      </div>

      <div className="form-group">
        <label htmlFor="password">密码:</label>
        <input
          type="password"
          id="password"
          name="password"
          value={formData.password}
          onChange={handleInputChange}
          onKeyDown={handleKeyDown}
          onFocus={handleFocus}
          onBlur={handleBlur}
          className={errors.password ? 'error' : ''}
        />
        {errors.password && <span className="error-message">{errors.password}</span>}
      </div>

      <div className="form-group">
        <label htmlFor="confirmPassword">确认密码:</label>
        <input
          type="password"
          id="confirmPassword"
          name="confirmPassword"
          value={formData.confirmPassword}
          onChange={handleInputChange}
          onKeyDown={handleKeyDown}
          onFocus={handleFocus}
          onBlur={handleBlur}
          className={errors.confirmPassword ? 'error' : ''}
        />
        {errors.confirmPassword && <span className="error-message">{errors.confirmPassword}</span>}
      </div>

      <div className="form-group checkbox-group">
        <label>
          <input type="checkbox" name="agreeToTerms" checked={formData.agreeToTerms} onChange={handleInputChange} />
          我同意服务条款
        </label>
        {errors.agreeToTerms && <span className="error-message">{errors.agreeToTerms}</span>}
      </div>

      <button type="submit" className="submit-button">
        注册
      </button>
    </form>
  )
}

2.5 自定义 JSX 元素与命名空间

自定义 JSX 元素类型

TypeScript 允许我们定义自定义的 JSX 元素类型,这在创建设计系统或组件库时特别有用:

// 声明自定义JSX元素
declare global {
  namespace JSX {
    interface IntrinsicElements {
      'custom-button': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
        variant?: 'primary' | 'secondary' | 'danger'
        size?: 'small' | 'medium' | 'large'
      }
      'custom-card': React.DetailedHTMLProps<React.HTMLAttributes<HTMLElement>, HTMLElement> & {
        title?: string
        bordered?: boolean
      }
    }
  }
}

// 使用自定义JSX元素
const CustomComponentExample: React.FC = () => {
  return (
    <div>
      <custom-button variant="primary" size="large" onClick={() => console.log('点击')}>
        主要按钮
      </custom-button>

      <custom-card title="卡片标题" bordered>
        <p>这是卡片内容</p>
      </custom-card>
    </div>
  )
}

// 扩展现有HTML元素
declare module 'react' {
  interface HTMLAttributes<T> {
    // 添加自定义属性
    'data-testid'?: string
    'data-analytics'?: string
  }
}

// 现在可以在任何HTML元素上使用这些属性
const EnhancedDiv: React.FC = () => {
  return (
    <div data-testid="enhanced-div" data-analytics="user-interaction" className="enhanced-component">
      增强的div元素
    </div>
  )
}

3. 三斜杠指令与模块解析 📁

3.1 三斜杠指令的设计理念

什么是三斜杠指令?

三斜杠指令(Triple-Slash Directives)是 TypeScript 中的特殊注释,用于向编译器提供额外的指令信息。它们必须出现在文件的顶部,在任何代码之前,并且以/// <reference开头。

三斜杠指令解决的核心问题:

  1. 类型定义引用:在模块系统之前,用于引用外部类型定义
  2. 编译器指令:为编译器提供特殊的处理指令
  3. 依赖声明:声明文件之间的依赖关系
  4. 全局类型扩展:扩展全局命名空间的类型

现代 TypeScript 中的使用场景:

虽然 ES6 模块系统已经成为主流,但三斜杠指令在某些特定场景下仍然有用:

  • 声明文件(.d.ts)中的类型引用
  • 全局类型定义的组织
  • 编译器特殊功能的启用
  • 与旧版本代码的兼容

3.2 类型引用指令详解

types 引用指令的使用场景

/// <reference types="..." />指令用于引用 npm 包中的类型定义,这是在模块系统普及之前管理类型依赖的重要方式:

// global-types.d.ts - 全局类型定义文件
/// <reference types="node" />
/// <reference types="jquery" />
/// <reference types="lodash" />

// 现在可以使用这些库的类型定义
declare global {
  interface Window {
    // 扩展Window对象
    customAPI: {
      version: string
      initialize(): Promise<void>
      config: {
        apiUrl: string
        timeout: number
      }
    }

    // jQuery全局对象(如果使用jQuery)
    $: JQueryStatic
    jQuery: JQueryStatic
  }

  // 扩展Node.js环境变量类型
  namespace NodeJS {
    interface ProcessEnv {
      NODE_ENV: 'development' | 'production' | 'test'
      API_KEY: string
      DATABASE_URL: string
      REDIS_URL?: string
      DEBUG_MODE?: string
    }

    // 扩展全局对象
    interface Global {
      __DEV__: boolean
      __TEST__: boolean
    }
  }
}

path 引用指令的文件组织

/// <reference path="..." />指令用于引用相对路径的类型定义文件,常用于组织大型项目的类型定义:

// types/index.d.ts - 主要类型定义入口
/// <reference path="./api.d.ts" />
/// <reference path="./user.d.ts" />
/// <reference path="./product.d.ts" />
/// <reference path="./utils.d.ts" />

// 这个文件作为所有类型定义的入口点
// 其他文件只需要引用这一个文件即可获得所有类型

// types/api.d.ts
interface ApiResponse<T = any> {
  success: boolean
  data: T
  message?: string
  timestamp: string
}

interface ApiError {
  code: number
  message: string
  details?: Record<string, any>
}

interface PaginatedResponse<T> extends ApiResponse<T[]> {
  pagination: {
    page: number
    limit: number
    total: number
    totalPages: number
  }
}

// types/user.d.ts
interface User {
  id: string
  username: string
  email: string
  profile: UserProfile
  permissions: UserPermission[]
  createdAt: string
  updatedAt: string
}

interface UserProfile {
  firstName: string
  lastName: string
  avatar?: string
  bio?: string
  location?: string
  website?: string
}

interface UserPermission {
  resource: string
  actions: ('read' | 'write' | 'delete' | 'admin')[]
}

// types/product.d.ts
interface Product {
  id: string
  name: string
  description: string
  price: number
  category: ProductCategory
  tags: string[]
  images: ProductImage[]
  inventory: ProductInventory
  status: 'active' | 'inactive' | 'discontinued'
}

interface ProductCategory {
  id: string
  name: string
  slug: string
  parentId?: string
}

interface ProductImage {
  id: string
  url: string
  alt: string
  order: number
}

interface ProductInventory {
  quantity: number
  reserved: number
  available: number
  lowStockThreshold: number
}

3.3 编译器指令的高级应用

库文件控制指令

编译器指令允许我们精确控制 TypeScript 编译器的行为,特别是在处理库文件和运行时环境时:

// custom-runtime.d.ts - 自定义运行时环境
/// <reference no-default-lib="true"/>
/// <reference lib="es2020" />
/// <reference lib="dom" />
/// <reference lib="webworker" />

// 禁用默认库,只包含我们需要的特定库
// 这在创建特殊运行环境(如Web Worker、Node.js扩展等)时很有用

// 定义自定义的全局环境
interface CustomRuntime {
  // 自定义控制台接口
  console: {
    log(message: any, ...args: any[]): void
    error(message: any, ...args: any[]): void
    warn(message: any, ...args: any[]): void
    debug(message: any, ...args: any[]): void
    trace(): void
  }

  // 自定义定时器
  setTimeout(callback: () => void, delay: number): number
  clearTimeout(id: number): void
  setInterval(callback: () => void, delay: number): number
  clearInterval(id: number): void

  // 自定义事件系统
  addEventListener(type: string, listener: (event: any) => void): void
  removeEventListener(type: string, listener: (event: any) => void): void
  dispatchEvent(event: any): boolean
}

// 声明全局运行时
declare const runtime: CustomRuntime

// 为特定环境提供类型支持
declare global {
  // Web Worker环境扩展
  interface WorkerGlobalScope {
    importScripts(...urls: string[]): void
    postMessage(message: any, transfer?: Transferable[]): void
  }

  // Service Worker环境扩展
  interface ServiceWorkerGlobalScope extends WorkerGlobalScope {
    registration: ServiceWorkerRegistration
    skipWaiting(): Promise<void>
    clients: Clients
  }
}

AMD 模块系统支持

虽然现代项目主要使用 ES 模块,但在某些遗留系统或特定环境中,AMD 模块系统仍然有用:

// amd-module-example.ts
/// <amd-module name="DataProcessor" />
/// <amd-dependency path="lodash" name="_" />
/// <amd-dependency path="moment" name="moment" />

// AMD模块定义,指定模块名称和依赖

// 声明外部依赖的类型
declare const _: typeof import('lodash')
declare const moment: typeof import('moment')

// 模块接口定义
export interface DataProcessorConfig {
  dateFormat: string
  locale: string
  precision: number
  enableCache: boolean
}

export interface ProcessedData {
  id: string
  processedAt: string
  data: any
  metadata: {
    processingTime: number
    cacheHit: boolean
  }
}

// 数据处理类
export class DataProcessor {
  private config: DataProcessorConfig
  private cache = new Map<string, ProcessedData>()

  constructor(config: Partial<DataProcessorConfig> = {}) {
    this.config = {
      dateFormat: 'YYYY-MM-DD HH:mm:ss',
      locale: 'zh-CN',
      precision: 2,
      enableCache: true,
      ...config,
    }
  }

  process(data: any[]): ProcessedData[] {
    const startTime = Date.now()

    const processed = data.map(item => {
      const id = this.generateId(item)

      // 检查缓存
      if (this.config.enableCache && this.cache.has(id)) {
        const cached = this.cache.get(id)!
        return {
          ...cached,
          metadata: {
            ...cached.metadata,
            cacheHit: true,
          },
        }
      }

      // 使用lodash处理数据
      const processedItem = _.cloneDeep(item)

      // 使用moment处理日期
      const processedAt = moment().format(this.config.dateFormat)

      const result: ProcessedData = {
        id,
        processedAt,
        data: processedItem,
        metadata: {
          processingTime: Date.now() - startTime,
          cacheHit: false,
        },
      }

      // 缓存结果
      if (this.config.enableCache) {
        this.cache.set(id, result)
      }

      return result
    })

    return processed
  }

  private generateId(data: any): string {
    // 使用lodash生成唯一ID
    return _.uniqueId(`data_${Date.now()}_`)
  }

  clearCache(): void {
    this.cache.clear()
  }

  getCacheSize(): number {
    return this.cache.size
  }
}

// 导出默认实例
export default new DataProcessor()

3.4 模块解析策略的实际应用

Node.js 风格的模块解析

TypeScript 的 Node 解析策略模拟了 Node.js 的模块解析行为,理解这个过程有助于解决模块导入问题:

// 项目结构示例
/*
src/
├── components/
│   ├── ui/
│   │   ├── Button/
│   │   │   ├── index.ts
│   │   │   ├── Button.tsx
│   │   │   └── Button.types.ts
│   │   └── Modal/
│   │       ├── index.ts
│   │       ├── Modal.tsx
│   │       └── Modal.types.ts
│   └── business/
│       ├── UserCard/
│       │   ├── index.ts
│       │   ├── UserCard.tsx
│       │   └── UserCard.types.ts
│       └── ProductList/
│           ├── index.ts
│           ├── ProductList.tsx
│           └── ProductList.types.ts
├── services/
│   ├── api/
│   │   ├── index.ts
│   │   ├── user.api.ts
│   │   └── product.api.ts
│   └── utils/
│       ├── index.ts
│       ├── date.utils.ts
│       └── validation.utils.ts
└── types/
    ├── index.ts
    ├── api.types.ts
    └── common.types.ts
*/

// src/components/ui/Button/index.ts - 组件入口文件
export { Button } from './Button'
export type { ButtonProps, ButtonVariant, ButtonSize } from './Button.types'

// src/components/ui/Button/Button.types.ts - 类型定义
export interface ButtonProps {
  variant?: ButtonVariant
  size?: ButtonSize
  disabled?: boolean
  loading?: boolean
  children: React.ReactNode
  onClick?: (event: React.MouseEvent<HTMLButtonElement>) => void
}

export type ButtonVariant = 'primary' | 'secondary' | 'danger' | 'ghost'
export type ButtonSize = 'small' | 'medium' | 'large'

// src/components/ui/Button/Button.tsx - 组件实现
import React from 'react'
import type { ButtonProps } from './Button.types'

export const Button: React.FC<ButtonProps> = ({ variant = 'primary', size = 'medium', disabled = false, loading = false, children, onClick }) => {
  return (
    <button className={`btn btn--${variant} btn--${size} ${loading ? 'btn--loading' : ''}`} disabled={disabled || loading} onClick={onClick}>
      {loading ? <span className="spinner" /> : children}
    </button>
  )
}

路径映射的企业级配置

在大型项目中,合理的路径映射配置可以大大提高开发效率和代码可维护性:

// tsconfig.json - 企业级路径映射配置
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      // 组件别名
      "@/components/*": ["components/*"],
      "@/ui/*": ["components/ui/*"],
      "@/business/*": ["components/business/*"],

      // 服务别名
      "@/services/*": ["services/*"],
      "@/api/*": ["services/api/*"],
      "@/utils/*": ["services/utils/*"],

      // 类型别名
      "@/types/*": ["types/*"],
      "@/types": ["types/index"],

      // 资源别名
      "@/assets/*": ["assets/*"],
      "@/images/*": ["assets/images/*"],
      "@/styles/*": ["assets/styles/*"],

      // 配置别名
      "@/config/*": ["config/*"],
      "@/constants/*": ["constants/*"],

      // 测试别名
      "@/test/*": ["../test/*"],
      "@/mocks/*": ["../test/mocks/*"],

      // 第三方库别名
      "~/*": ["../node_modules/*"]
    }
  }
}

使用路径映射的实际代码示例:

// src/pages/UserManagement.tsx - 使用路径映射的页面组件
import React, { useState, useEffect } from 'react'

// 使用路径映射导入组件
import { Button } from '@/ui/Button'
import { Modal } from '@/ui/Modal'
import { UserCard } from '@/business/UserCard'

// 使用路径映射导入服务
import { userApi } from '@/api/user.api'
import { validateEmail, formatDate } from '@/utils'

// 使用路径映射导入类型
import type { User, ApiResponse } from '@/types'
import type { UserCardProps } from '@/business/UserCard'

// 使用路径映射导入常量
import { USER_ROLES, API_ENDPOINTS } from '@/constants'

interface UserManagementProps {
  initialUsers?: User[]
}

export const UserManagement: React.FC<UserManagementProps> = ({ initialUsers = [] }) => {
  const [users, setUsers] = useState<User[]>(initialUsers)
  const [loading, setLoading] = useState(false)
  const [selectedUser, setSelectedUser] = useState<User | null>(null)
  const [showModal, setShowModal] = useState(false)

  // 加载用户列表
  const loadUsers = async (): Promise<void> => {
    setLoading(true)
    try {
      const response: ApiResponse<User[]> = await userApi.getUsers()
      if (response.success) {
        setUsers(response.data)
      }
    } catch (error) {
      console.error('加载用户失败:', error)
    } finally {
      setLoading(false)
    }
  }

  // 处理用户选择
  const handleUserSelect = (user: User): void => {
    setSelectedUser(user)
    setShowModal(true)
  }

  // 处理用户更新
  const handleUserUpdate = async (updatedUser: Partial<User>): Promise<void> => {
    if (!selectedUser) return

    try {
      const response = await userApi.updateUser(selectedUser.id, updatedUser)
      if (response.success) {
        setUsers(prev => prev.map(user => (user.id === selectedUser.id ? { ...user, ...updatedUser } : user)))
        setShowModal(false)
        setSelectedUser(null)
      }
    } catch (error) {
      console.error('更新用户失败:', error)
    }
  }

  useEffect(() => {
    if (initialUsers.length === 0) {
      loadUsers()
    }
  }, [])

  return (
    <div className="user-management">
      <div className="user-management__header">
        <h1>用户管理</h1>
        <Button variant="primary" onClick={loadUsers} loading={loading}>
          刷新用户列表
        </Button>
      </div>

      <div className="user-management__content">
        {loading ? (
          <div className="loading">加载中...</div>
        ) : (
          <div className="user-grid">
            {users.map(user => (
              <UserCard key={user.id} user={user} onSelect={() => handleUserSelect(user)} showRole={true} showLastLogin={true} />
            ))}
          </div>
        )}
      </div>

      {showModal && selectedUser && (
        <Modal title="编辑用户" onClose={() => setShowModal(false)}>
          <UserEditForm user={selectedUser} onSave={handleUserUpdate} onCancel={() => setShowModal(false)} />
        </Modal>
      )}
    </div>
  )
}

// 用户编辑表单组件
interface UserEditFormProps {
  user: User
  onSave: (user: Partial<User>) => Promise<void>
  onCancel: () => void
}

const UserEditForm: React.FC<UserEditFormProps> = ({ user, onSave, onCancel }) => {
  const [formData, setFormData] = useState({
    email: user.email,
    role: user.role,
    isActive: user.isActive,
  })

  const [errors, setErrors] = useState<Record<string, string>>({})

  const handleSubmit = async (e: React.FormEvent): Promise<void> => {
    e.preventDefault()

    // 验证表单
    const newErrors: Record<string, string> = {}

    if (!validateEmail(formData.email)) {
      newErrors.email = '请输入有效的邮箱地址'
    }

    if (!USER_ROLES.includes(formData.role)) {
      newErrors.role = '请选择有效的用户角色'
    }

    if (Object.keys(newErrors).length > 0) {
      setErrors(newErrors)
      return
    }

    await onSave(formData)
  }

  return (
    <form onSubmit={handleSubmit} className="user-edit-form">
      <div className="form-group">
        <label htmlFor="email">邮箱:</label>
        <input
          type="email"
          id="email"
          value={formData.email}
          onChange={e => setFormData(prev => ({ ...prev, email: e.target.value }))}
          className={errors.email ? 'error' : ''}
        />
        {errors.email && <span className="error-message">{errors.email}</span>}
      </div>

      <div className="form-group">
        <label htmlFor="role">角色:</label>
        <select id="role" value={formData.role} onChange={e => setFormData(prev => ({ ...prev, role: e.target.value }))} className={errors.role ? 'error' : ''}>
          {USER_ROLES.map(role => (
            <option key={role} value={role}>
              {role}
            </option>
          ))}
        </select>
        {errors.role && <span className="error-message">{errors.role}</span>}
      </div>

      <div className="form-group">
        <label>
          <input type="checkbox" checked={formData.isActive} onChange={e => setFormData(prev => ({ ...prev, isActive: e.target.checked }))} />
          激活状态
        </label>
      </div>

      <div className="form-actions">
        <Button type="submit" variant="primary">
          保存
        </Button>
        <Button type="button" variant="secondary" onClick={onCancel}>
          取消
        </Button>
      </div>
    </form>
  )
}

3.3 模块解析策略详解

TypeScript 模块解析的工作原理

TypeScript 支持两种主要的模块解析策略:

  1. Classic 解析策略(已弃用)
  2. Node 解析策略(推荐使用)

Node 解析策略的详细过程:

// 相对导入的解析过程
import { UserService } from './services/UserService'

// TypeScript会按以下顺序查找:
// 1. ./services/UserService.ts
// 2. ./services/UserService.tsx
// 3. ./services/UserService.d.ts
// 4. ./services/UserService/package.json (查找"types"字段)
// 5. ./services/UserService/index.ts
// 6. ./services/UserService/index.tsx
// 7. ./services/UserService/index.d.ts

// 非相对导入的解析过程
import { lodash } from 'lodash'

// TypeScript会在node_modules中查找:
// 1. node_modules/lodash.ts
// 2. node_modules/lodash.tsx
// 3. node_modules/lodash.d.ts
// 4. node_modules/lodash/package.json (查找"types"或"typings"字段)
// 5. node_modules/lodash/index.ts
// 6. node_modules/lodash/index.tsx
// 7. node_modules/lodash/index.d.ts
// 8. node_modules/@types/lodash/... (查找@types包)

路径映射配置

// tsconfig.json中的路径映射配置
{
  "compilerOptions": {
    "baseUrl": "./src",
    "paths": {
      "@/*": ["*"], // @/components -> src/components
      "@components/*": ["components/*"], // 组件别名
      "@utils/*": ["utils/*"], // 工具函数别名
      "@services/*": ["services/*"], // 服务别名
      "@types/*": ["types/*"], // 类型定义别名
      "~/*": ["../node_modules/*"] // node_modules别名
    },
    "moduleResolution": "node",
    "esModuleInterop": true,
    "allowSyntheticDefaultImports": true
  }
}

使用路径映射的实际示例:

// 使用路径映射前
import { UserService } from '../../../services/user/UserService'
import { ValidationUtils } from '../../../utils/validation/ValidationUtils'
import { ApiResponse } from '../../../types/api/ApiResponse'

// 使用路径映射后
import { UserService } from '@services/user/UserService'
import { ValidationUtils } from '@utils/validation/ValidationUtils'
import { ApiResponse } from '@types/api/ApiResponse'

// 组件中的使用示例
import React from 'react'
import { Button } from '@components/ui/Button'
import { Modal } from '@components/ui/Modal'
import { useUserData } from '@/hooks/useUserData'
import { formatDate } from '@utils/date'

interface UserProfileProps {
  userId: string
}

const UserProfile: React.FC<UserProfileProps> = ({ userId }) => {
  const { user, loading, error } = useUserData(userId)

  if (loading) return <div>Loading...</div>
  if (error) return <div>Error: {error.message}</div>
  if (!user) return <div>User not found</div>

  return (
    <div className="user-profile">
      <h1>{user.name}</h1>
      <p>注册时间: {formatDate(user.createdAt)}</p>
      <Button onClick={() => console.log('Edit user')}>编辑用户</Button>
    </div>
  )
}

export default UserProfile

4. 类型兼容性与类型推断 🔍

4.1 结构化类型系统的设计理念

什么是结构化类型系统?

TypeScript 采用结构化类型系统(Structural Type System),也称为"鸭子类型"(Duck Typing)。这意味着类型的兼容性基于成员的结构,而不是显式的声明。如果两个类型具有相同的结构,它们就被认为是兼容的。

结构化类型系统 vs 标名类型系统:

// 结构化类型系统示例
interface Point2D {
  x: number
  y: number
}

interface Vector2D {
  x: number
  y: number
}

// 虽然是不同的接口名,但结构相同,因此兼容
function processPoint(point: Point2D): void {
  console.log(`Point: (${point.x}, ${point.y})`)
}

const vector: Vector2D = { x: 10, y: 20 }
processPoint(vector) // ✅ 正确,结构兼容

// 额外属性也是兼容的(多的属性不影响兼容性)
interface Point3D {
  x: number
  y: number
  z: number
}

const point3D: Point3D = { x: 1, y: 2, z: 3 }
processPoint(point3D) // ✅ 正确,Point3D包含Point2D的所有属性

类型兼容性的核心原则:

  1. 协变性(Covariance):子类型可以赋值给父类型
  2. 逆变性(Contravariance):在函数参数中,父类型可以赋值给子类型
  3. 双变性(Bivariance):TypeScript 在某些情况下同时支持协变和逆变
  4. 不变性(Invariance):类型必须完全匹配

4.2 函数类型兼容性详解

参数兼容性:逆变原理

函数参数的兼容性遵循逆变(contravariance)原则,这意味着参数类型更宽泛的函数可以赋值给参数类型更具体的函数类型。这个设计确保了类型安全:

// 基础事件处理示例
interface BaseEvent {
  type: string
  timestamp: number
}

interface MouseEvent extends BaseEvent {
  type: 'click' | 'mousedown' | 'mouseup'
  clientX: number
  clientY: number
  button: number
}

interface KeyboardEvent extends BaseEvent {
  type: 'keydown' | 'keyup' | 'keypress'
  key: string
  code: string
  ctrlKey: boolean
  shiftKey: boolean
}

// 定义事件处理器类型
type BaseEventHandler = (event: BaseEvent) => void
type MouseEventHandler = (event: MouseEvent) => void
type KeyboardEventHandler = (event: KeyboardEvent) => void

// 通用事件处理器(处理基础事件)
const handleBaseEvent: BaseEventHandler = event => {
  console.log(`事件类型: ${event.type}, 时间: ${event.timestamp}`)
}

// 逆变性:基础事件处理器可以处理更具体的事件
const handleMouseEvent: MouseEventHandler = handleBaseEvent // ✅ 正确
const handleKeyboardEvent: KeyboardEventHandler = handleBaseEvent // ✅ 正确

// 但反过来不行
const specificMouseHandler: MouseEventHandler = event => {
  console.log(`鼠标事件: ${event.type} at (${event.clientX}, ${event.clientY})`)
}

// const generalHandler: BaseEventHandler = specificMouseHandler; // ❌ 错误
// 因为specificMouseHandler期望的是MouseEvent,但可能接收到KeyboardEvent

返回值兼容性:协变原理

函数返回值的兼容性遵循协变(covariance)原则,返回更具体类型的函数可以赋值给返回更宽泛类型的函数:

// 动物类型层次结构
interface Animal {
  name: string
  age: number
  makeSound(): string
}

interface Dog extends Animal {
  breed: string
  wagTail(): void
}

interface Cat extends Animal {
  breed: string
  purr(): void
}

// 工厂函数类型定义
type AnimalFactory = () => Animal
type DogFactory = () => Dog
type CatFactory = () => Cat

// 具体的工厂实现
const createDog: DogFactory = () => ({
  name: 'Buddy',
  age: 3,
  breed: 'Golden Retriever',
  makeSound: () => 'Woof!',
  wagTail: () => console.log('Wagging tail!'),
})

const createCat: CatFactory = () => ({
  name: 'Whiskers',
  age: 2,
  breed: 'Persian',
  makeSound: () => 'Meow!',
  purr: () => console.log('Purring...'),
})

// 协变性:返回Dog的函数可以赋值给返回Animal的函数
const animalFactory1: AnimalFactory = createDog // ✅ 正确
const animalFactory2: AnimalFactory = createCat // ✅ 正确

// 使用工厂函数
const animal1 = animalFactory1() // 类型为Animal,但实际是Dog
const animal2 = animalFactory2() // 类型为Animal,但实际是Cat

console.log(animal1.makeSound()) // "Woof!"
console.log(animal2.makeSound()) // "Meow!"

复杂函数兼容性场景

在实际开发中,函数兼容性的应用场景往往更加复杂,涉及多层嵌套和泛型:

// 数据处理管道示例
interface DataProcessor<TInput, TOutput> {
  process(input: TInput): TOutput
  validate(input: TInput): boolean
}

interface StringProcessor extends DataProcessor<string, string> {
  process(input: string): string
  validate(input: string): boolean
  getLength(input: string): number
}

interface NumberProcessor extends DataProcessor<number, number> {
  process(input: number): number
  validate(input: number): boolean
  isPositive(input: number): boolean
}

// 处理器工厂类型
type ProcessorFactory<T> = () => DataProcessor<T, T>
type StringProcessorFactory = () => StringProcessor
type NumberProcessorFactory = () => NumberProcessor

// 具体实现
const createStringProcessor: StringProcessorFactory = () => ({
  process: (input: string) => input.trim().toLowerCase(),
  validate: (input: string) => input.length > 0,
  getLength: (input: string) => input.length,
})

const createNumberProcessor: NumberProcessorFactory = () => ({
  process: (input: number) => Math.abs(input),
  validate: (input: number) => !isNaN(input),
  isPositive: (input: number) => input > 0,
})

// 协变性应用
const stringFactory: ProcessorFactory<string> = createStringProcessor // ✅ 正确
const numberFactory: ProcessorFactory<number> = createNumberProcessor // ✅ 正确

// 处理器管理器
class ProcessorManager {
  private processors = new Map<string, DataProcessor<any, any>>()

  // 注册处理器(利用协变性)
  registerProcessor<T>(key: string, factory: ProcessorFactory<T>): void {
    this.processors.set(key, factory())
  }

  // 获取处理器
  getProcessor<T>(key: string): DataProcessor<T, T> | undefined {
    return this.processors.get(key)
  }

  // 批量处理数据
  processData<T>(key: string, data: T[]): T[] {
    const processor = this.getProcessor<T>(key)
    if (!processor) {
      throw new Error(`处理器 ${key} 未找到`)
    }

    return data.filter(item => processor.validate(item)).map(item => processor.process(item))
  }
}

// 使用示例
const manager = new ProcessorManager()
manager.registerProcessor('string', createStringProcessor)
manager.registerProcessor('number', createNumberProcessor)

const processedStrings = manager.processData('string', ['  Hello  ', '  WORLD  ', ''])
const processedNumbers = manager.processData('number', [-5, 10, NaN, 0])

console.log(processedStrings) // ['hello', 'world']
console.log(processedNumbers) // [5, 10, 0]
4.2.4 函数重载与兼容性

TypeScript 的函数重载为函数兼容性增加了额外的复杂性。

格式化器接口定义

// 函数重载示例
interface Formatter {
  // 重载签名
  format(value: string): string
  format(value: number): string
  format(value: Date): string
  format(value: boolean): string

  // 实现签名
  format(value: string | number | Date | boolean): string
}

默认格式化器实现

// 具体实现
class DefaultFormatter implements Formatter {
  format(value: string): string
  format(value: number): string
  format(value: Date): string
  format(value: boolean): string
  format(value: string | number | Date | boolean): string {
    if (typeof value === 'string') {
      return `"${value}"`
    } else if (typeof value === 'number') {
      return value.toFixed(2)
    } else if (value instanceof Date) {
      return value.toISOString().split('T')[0]
    } else if (typeof value === 'boolean') {
      return value ? '是' : '否'
    }
    return String(value)
  }
}

精确格式化器实现

// 更具体的格式化器
class PreciseFormatter implements Formatter {
  format(value: string): string
  format(value: number): string
  format(value: Date): string
  format(value: boolean): string
  format(value: string | number | Date | boolean): string {
    if (typeof value === 'string') {
      return `字符串: "${value}"`
    } else if (typeof value === 'number') {
      return `数字: ${value.toFixed(4)}`
    } else if (value instanceof Date) {
      return `日期: ${value.toLocaleDateString('zh-CN')}`
    } else if (typeof value === 'boolean') {
      return `布尔值: ${value ? '真' : '假'}`
    }
    return `未知类型: ${String(value)}`
  }
}

格式化服务应用

// 格式化器工厂
type FormatterFactory = () => Formatter

const createDefaultFormatter: FormatterFactory = () => new DefaultFormatter()
const createPreciseFormatter: FormatterFactory = () => new PreciseFormatter()

// 格式化服务
class FormattingService {
  private formatter: Formatter

  constructor(formatterFactory: FormatterFactory) {
    this.formatter = formatterFactory()
  }

  formatValue(value: string | number | Date | boolean): string {
    return this.formatter.format(value as any)
  }

  formatMultiple(values: (string | number | Date | boolean)[]): string[] {
    return values.map(value => this.formatValue(value))
  }
}

使用示例

// 使用示例
const defaultService = new FormattingService(createDefaultFormatter)
const preciseService = new FormattingService(createPreciseFormatter)

const testValues: (string | number | Date | boolean)[] = ['Hello World', 3.14159, new Date(), true]

console.log('默认格式化:', defaultService.formatMultiple(testValues))
console.log('精确格式化:', preciseService.formatMultiple(testValues))

4.3 类型推断的智能化机制

4.3.1 上下文类型推断的工作原理

TypeScript 的上下文类型推断是编译器的一项强大功能,它能根据代码的使用上下文自动推断出合适的类型,大大减少了显式类型注解的需要。

基础类型推断示例

// 基础上下文推断示例
interface User {
  id: number
  name: string
  email: string
  role: 'admin' | 'moderator' | 'user'
  isActive: boolean
  lastLogin?: Date
}

数组字面量类型推断

// TypeScript基于数组字面量推断类型
const users = [
  { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin', isActive: true },
  { id: 2, name: 'Bob', email: 'bob@example.com', role: 'user', isActive: false },
  { id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'moderator', isActive: true },
]
// 推断为: { id: number; name: string; email: string; role: string; isActive: boolean; }[]

// 如果我们想要更精确的类型,可以使用类型断言或显式声明
const typedUsers: User[] = [
  { id: 1, name: 'Alice', email: 'alice@example.com', role: 'admin', isActive: true },
  { id: 2, name: 'Bob', email: 'bob@example.com', role: 'user', isActive: false },
  { id: 3, name: 'Charlie', email: 'charlie@example.com', role: 'moderator', isActive: true },
]

数组方法的上下文推断

// 数组方法的上下文推断
const userNames = typedUsers.map(user => user.name) // 推断为 string[]
const activeUsers = typedUsers.filter(user => user.isActive) // 推断为 User[]
const adminUsers = typedUsers.filter(user => user.role === 'admin') // 推断为 User[]

// 链式调用的类型推断
const adminNames = typedUsers
  .filter(user => user.role === 'admin') // User[]
  .map(user => user.name) // string[]
  .sort() // string[]

// 对象方法的上下文推断
const userById = typedUsers.reduce((acc, user) => {
  acc[user.id] = user
  return acc
}, {} as Record<number, User>) // 推断为 Record<number, User>
4.3.2 事件处理器的上下文推断

在事件处理中,TypeScript 能够根据事件类型自动推断参数类型。

事件监听器类型映射

// 事件监听器类型映射
interface EventListenerMap {
  click: (event: MouseEvent) => void
  keydown: (event: KeyboardEvent) => void
  keyup: (event: KeyboardEvent) => void
  focus: (event: FocusEvent) => void
  blur: (event: FocusEvent) => void
  scroll: (event: Event) => void
  resize: (event: UIEvent) => void
  load: (event: Event) => void
  error: (event: ErrorEvent) => void
}

// 通用事件监听器函数
function addEventListener<K extends keyof EventListenerMap>(
  element: HTMLElement | Window | Document,
  type: K,
  listener: EventListenerMap[K],
  options?: boolean | AddEventListenerOptions,
): void {
  element.addEventListener(type, listener as EventListener, options)
}

事件类型自动推断示例

// 使用时TypeScript自动推断event的类型
const button = document.querySelector('button')!

addEventListener(button, 'click', event => {
  // event被自动推断为MouseEvent
  console.log(`按钮被点击,坐标: (${event.clientX}, ${event.clientY})`)
  console.log(`点击的按钮: ${event.button}`) // 0=左键, 1=中键, 2=右键
})

addEventListener(document, 'keydown', event => {
  // event被自动推断为KeyboardEvent
  console.log(`按键: ${event.key}, 代码: ${event.code}`)
  if (event.ctrlKey && event.key === 's') {
    event.preventDefault()
    console.log('拦截了Ctrl+S快捷键')
  }
})

addEventListener(window, 'resize', event => {
  // event被自动推断为UIEvent
  console.log(`窗口大小改变: ${window.innerWidth}x${window.innerHeight}`)
})

自定义事件处理器

// 自定义事件处理器
interface CustomEventMap {
  'user-login': (event: CustomEvent<{ userId: number; username: string }>) => void
  'data-loaded': (event: CustomEvent<{ count: number; timestamp: Date }>) => void
  'error-occurred': (event: CustomEvent<{ message: string; code: number }>) => void
}

class EventEmitter {
  private listeners = new Map<string, Function[]>()

  on<K extends keyof CustomEventMap>(event: K, listener: CustomEventMap[K]): void {
    if (!this.listeners.has(event)) {
      this.listeners.set(event, [])
    }
    this.listeners.get(event)!.push(listener)
  }

  emit<K extends keyof CustomEventMap>(
    event: K,
    data: K extends keyof CustomEventMap ? (CustomEventMap[K] extends (event: CustomEvent<infer T>) => void ? T : never) : never,
  ): void {
    const eventListeners = this.listeners.get(event)
    if (eventListeners) {
      const customEvent = new CustomEvent(event, { detail: data })
      eventListeners.forEach(listener => listener(customEvent))
    }
  }
}

自定义事件使用示例

// 使用自定义事件发射器
const emitter = new EventEmitter()

emitter.on('user-login', event => {
  // event.detail被推断为 { userId: number; username: string }
  console.log(`用户登录: ${event.detail.username} (ID: ${event.detail.userId})`)
})

emitter.on('data-loaded', event => {
  // event.detail被推断为 { count: number; timestamp: Date }
  console.log(`数据加载完成: ${event.detail.count}条记录,时间: ${event.detail.timestamp}`)
})

// 发射事件时类型安全
emitter.emit('user-login', { userId: 123, username: 'alice' })
emitter.emit('data-loaded', { count: 50, timestamp: new Date() })
4.3.3 条件类型中的 infer 推断

infer关键字是 TypeScript 条件类型中的强大工具,用于在类型匹配过程中推断并提取类型。

基础 infer 用法

// 基础infer用法
type GetArrayElementType<T> = T extends (infer U)[] ? U : never
type StringArrayElement = GetArrayElementType<string[]> // string
type NumberArrayElement = GetArrayElementType<number[]> // number
type MixedArrayElement = GetArrayElementType<(string | number)[]> // string | number

// 函数类型推断
type GetFunctionReturnType<T> = T extends (...args: any[]) => infer R ? R : never
type GetFunctionParameters<T> = T extends (...args: infer P) => any ? P : never

函数类型推断示例

// 示例函数
function processUserData(
  userId: number,
  userData: { name: string; email: string },
  options?: { validate: boolean },
): Promise<{ success: boolean; user: User }> {
  // 实现逻辑...
  return Promise.resolve({ success: true, user: {} as User })
}

type ProcessReturnType = GetFunctionReturnType<typeof processUserData>
// Promise<{ success: boolean; user: User }>

type ProcessParameters = GetFunctionParameters<typeof processUserData>
// [number, { name: string; email: string }, { validate: boolean }?]

Promise 类型推断

// Promise类型推断
type UnwrapPromise<T> = T extends Promise<infer U> ? (U extends Promise<any> ? UnwrapPromise<U> : U) : T

type SimplePromise = UnwrapPromise<Promise<string>> // string
type NestedPromise = UnwrapPromise<Promise<Promise<number>>> // number
type TripleNestedPromise = UnwrapPromise<Promise<Promise<Promise<boolean>>>> // boolean

复杂递归类型推断

// 复杂的递归类型推断
type DeepReadonly<T> = {
  readonly [P in keyof T]: T[P] extends object ? (T[P] extends Function ? T[P] : DeepReadonly<T[P]>) : T[P]
}

type FlattenArray<T> = T extends (infer U)[] ? (U extends (infer V)[] ? FlattenArray<U> : U) : T

type NestedStringArray = string[][][]
type FlattenedType = FlattenArray<NestedStringArray> // string

对象属性类型推断

// 对象属性类型推断
type GetObjectValueType<T, K> = K extends keyof T ? T[K] : never
type GetNestedPropertyType<T, K> = K extends `${infer P}.${infer Rest}`
  ? P extends keyof T
    ? GetNestedPropertyType<T[P], Rest>
    : never
  : K extends keyof T
  ? T[K]
  : never

interface NestedUser {
  id: number
  profile: {
    personal: {
      name: string
      age: number
    }
    contact: {
      email: string
      phone: string
    }
  }
  settings: {
    theme: 'light' | 'dark'
    notifications: boolean
  }
}

type UserName = GetNestedPropertyType<NestedUser, 'profile.personal.name'> // string
type UserEmail = GetNestedPropertyType<NestedUser, 'profile.contact.email'> // string
type UserTheme = GetNestedPropertyType<NestedUser, 'settings.theme'> // 'light' | 'dark'

实用的类型推断工具

// 实用的类型推断工具
type NonFunctionPropertyNames<T> = {
  [K in keyof T]: T[K] extends Function ? never : K
}[keyof T]

type NonFunctionProperties<T> = Pick<T, NonFunctionPropertyNames<T>>

class UserService {
  private apiUrl: string = '/api/users'
  private timeout: number = 5000

  async getUser(id: number): Promise<User> {
    // 实现逻辑...
    return {} as User
  }

  async updateUser(id: number, data: Partial<User>): Promise<User> {
    // 实现逻辑...
    return {} as User
  }

  validateEmail(email: string): boolean {
    return email.includes('@')
  }
}

type ServiceProperties = NonFunctionProperties<UserService>
// { apiUrl: string; timeout: number }

// 这样我们就可以只提取类的属性,不包括方法
const serviceConfig: ServiceProperties = {
  apiUrl: '/api/v2/users',
  timeout: 10000,
}

🎯 学习总结

通过第四天的深入学习,我们全面掌握了 TypeScript 高级特性和类型系统的精髓:

核心知识体系:

  • 🛠️ 实用工具类型大全 - 掌握 TypeScript 内置的类型操作瑞士军刀
  • 🎭 TypeScript 与 JSX - 理解类型安全的 UI 开发范式
  • 📁 三斜杠指令与模块解析 - 深入编译器指令和模块系统
  • 🔍 类型兼容性与类型推断 - 掌握结构化类型系统的核心原理

通过第四天的学习,你已经具备了 TypeScript 高级开发者的核心技能。这些知识将成为你构建复杂、可维护、类型安全应用程序的坚实基础。继续实践和探索,成为 TypeScript 专家的路上,你已经迈出了关键的一步! 🎉

昨天以前首页

Flip-js 优雅的处理元素结构变化的动画

作者 凡二人
2025年6月28日 09:48

介绍

基于 FLIP 动画思想,处理元素结构变化的动画,并且同时处理元素样式变化引起的动画

demo.gif

动画只要三步:

  1. 记录起始状态
  2. 自由改变元素结构和样式,移动元素到要去的地方
  3. 执行动画

gitee地址

github地址

安装教程

npm install fan-flip-js

使用说明

  1. 引入 flip-js
import { Flip } from 'fan-flip-js';
  1. 创建 Flip 实例
const flip = new Flip(el[, animateOptionsOrDuration][, otherStyleKeys]);
  • 第一个参数 el 是必传的,用于指定要动画的元素。可以是单个元素,也可以是元素组成的数组,或者 NodeList 等。
  • 第二个参数 animateOptionsOrDuration 是可选的,用于指定动画的配置
    • 如果是对象,那么可以指定 durationeasingdelay 等属性
    • 如果是数字,那么会作为 duration 属性的值
  • 第三个参数 otherStyleKeys 是可选的,用于指定除了 transform 以外的其他样式属性,这些属性也会参与动画。

需要注意的是,传入的 key 对应的样式必须支持过渡/动画。例如 display 和背景渐变颜色目前还不支持过渡。背景渐变颜色的实现需要使用最新的 Houdini API 中的 CSS property 才能实现,这就属于另一块知识了。

  1. 自由改变元素结构,和各种 CSS 样式
// 改变元素结构
parentEl.appendChild(el);

// 改变元素样式
el.style.width = '200px';
el.style.backgroundColor = 'red';
  1. 调用实例的 animate 方法,执行动画
flip.animate();

调用 animate 方法时,可以传入参数,支持 Promise 和回调函数两种方式实现动画结束后的回调。

当第一次动画结束后,下一次调用 animate 方法时也可以传入 animateOption,用于指定本次动画的配置。

  • animate(animateOption?: IAnimateOption): Promise<void>;
  • animate(callback: () => void, animateOption?: IAnimateOption): void;
  1. destroy 方法,销毁实例。当不再需要动画时,调用该方法销毁实例,释放内存。这一步是必要的,为了保证一个 DOM 元素同时只能创建一个 Flip 实例,也保证了动画的正确执行和防止内存泄漏。
flip.destroy();

除此之外,Flip 实例还提供了以下方法:

  • pause 方法,暂停动画
  • resume 方法,恢复动画

示例

这是上面 demo 的代码

import { Flip } from "./flip";

const btn = document.querySelector('button')!;
const ul = document.querySelector('ul')!;
const lis = Array.from(ul.children);
btn.addEventListener('click', function() {

  const flip = new Flip(lis as HTMLElement[], 1000, ['backgroundColor', 'width']);
  lis.sort(() => Math.random() - 0.5).forEach((item) => {
    (item as HTMLElement).style.backgroundColor = getRandomColor();
    (item as HTMLElement).style.width = getRandomNumber(100, 300) + 'px';
    ul.appendChild(item);
  });

  await flip.animate();
  console.log('Animation finished');
  flip.destroy();
});

function getRandomColor() {
  return '#' + Math.random().toString(16).substring(2, 8);
}

function getRandomNumber(min: number, max: number) {
  return Math.floor(Math.random() * (max - min + 1)) + min;
}

也支持同时包含其他复杂 transform 变化(旋转,缩放)的动画,例如:

demo2.gif


终于完成我的第一个 npm 包!!!

【TypeScript专栏】二、TypeScript基础(一)

作者 engineer_why
2025年6月26日 18:41

2.1 JavaScript 基础知识补充

  • 数据类型

    • 7种原始类型

      • Boolean 布尔
      • Null 空
      • Undefined 未定义
      • Number 数字
      • BigInt 任意精度的整数

        它解决了传统 Number 类型因使用 64 位浮点数格式而无法安全表示超大整数(超过 ±(2^53 - 1))的问题。

      • String 字符串
      • Symbol 唯一
    • Object

      除 Object 以外的所有类型都是不可变的(值本身无法被改变)。称这些类型的值为“原始值”。

  • 类数组

    • 是普通对象,具有 length 属性和数字索引访问能力,但不继承 Array.prototype,因此无法直接调用数组方法。

    • 区别

      • 数组:是 JavaScript 的内置对象,用于存储有序的元素集合,继承自 Array.prototype,拥有丰富的数组方法(如 pushmapforEach 等)。
      • image.png
    • 转换为数组的方法

      // Array.from() (推荐)
      const argsArray = Array.from(arguments);
      
      // 扩展运算符 ...
      const nodesArray = [...doucment.querySelectorAll('div')];
      
      // Array.prototype.slice.call() (旧版本兼容)
      const argsArray = Array.prototype.slice.call(arguments);
      

2.2 原始数据类型

// 布尔类型
let isDone: boolean = false;

// 数字类型
let age: number = 12;

// 字符串类型
let firstName: string = 'zhangsan';
let message: string = `Hello,${firstName}`

// null 和 undifined
// 这两种类型是所有类型的子类型
// 也就是说这两种类型值可以赋值给其他类型
let u: undefined = undefined;
let n: null = null;

2.3 Any类型

  • 在无法判断所写数据的类型时,或临时完善ts校验时,可用。
  • 允许赋值给任意类型。
  • 一般情况下,要避免使用any类型,因为这个类型可以任意调用方法和属性,就会更容易出现错误,丧失类型检查的作用。
let notSure: any = 4;
notSure = 'maybe a string';
notSure = true;

notSure.myName;
notSure.getName(); 
// 调用属性和方法均不会报错,因为默认为any 任意类型

2.4 数组

// 声明 数字类型的数组
// 数组的元素,不允许出现 数字以外的类型
let arrOfNumber: number[] = [1,2,3];

// 在指定好类型后,在调用这个数组后
// 可以自动获得数组上所有的方法
// (输入 数组名.  后,存在的方法会自动弹出提示框)
arrOfNumber.push(5);

image.png

2.5 类数组

function test() {
    console.log(arguments);
    // arguments 就是类数组
    // 而类数组有已经内置的类型 IArguments 类型
    // 还有诸多已经存在的内置类型
}

2.6 元组

  • 合并不同类型的对象 将内部的数据类型进行更精细的确定
  • 起源于函数式编程
// 存储的数组元素,顺序和类型必须对应
// 因为仍旧是数组,所以可以使用数组的方法
// 如 push,但只可添加设定好的两种类型的值
let user: [string, number] = ['zhangsan', 20];

如何在TypeScript里使用类封装枚举来实现Java的枚举形参倒置

作者 Hamm
2025年4月11日 15:43

一、前言

首先,枚举形参倒置 的意思是通过为枚举形参添加一些方法,来减少调用时候传入的形参个数。

🌰举个栗子

long timestamp = System.currentTimeMillis();

// before
DateTimeUtil.format(timestamp, DateTimeFormatter.FULL_DATE);

// after
DateTimeFormatter.FULL_DATE.format(timestamp);

如上示例代码,我们可以在调用时候减少传入一个枚举形参的传入,写出来的代码会更加简洁好看。

还有其他好处吗?

好像没有了...

当然,我们不讨论这两种写法的好处和坏处 (评论区可以讨论) ,我们只聊实现。

二、Java 的实现

众所周知,在 Java 中,枚举本身也是类的特殊封装,而枚举项可以认为是 Java 类的静态实例常量,所以,我们很轻易的就能实现:

2.1 封装枚举类

@Getter
@AllArgsConstructor
public enum DateTimeFormatter {
    /**
     * 年月日
     */
    FULL_DATE("yyyy-MM-dd"),

    /**
     * 时分秒
     */
    FULL_TIME("HH:mm:ss"),

    /**
     * 年月日时分秒
     */
    FULL_DATETIME("yyyy-MM-dd HH:mm:ss"),
    ;

    private final String value;
}

如上,我们声明这个枚举类之后,就可以为这个枚举添加一些我们需要的方法了:

2.2 添加方法

@Getter
@AllArgsConstructor
public enum DateTimeFormatter {
    // 省略定义的枚举项目
    ;

    // 枚举项封装的值
    private final String value;

    /**
     * 使用这个模板格式化毫秒时间戳
     *
     * @param milliSecond 毫秒时间戳
     * @return 格式化后的字符串
     */
    public final @NotNull String format(long milliSecond) {
        return DateTimeUtil.format(milliSecond, this);
    }

    /**
     * 使用这个模板格式化当前时间
     *
     * @return 格式化后的字符串
     */
    public final @NotNull String formatCurrent() {
        return format(System.currentTimeMillis());
    }
}

2.3 调用示例

所以封装完毕之后,我们就可以用这两个方法来实现本文提到的 枚举形参倒置 了:

QQ_1744356483132.png

三、TypeScript 的实现

可是在 TypeScript 中,枚举 并没有像 Java 那样,有 枚举封装 的特性,所以,我们只能通过 来实现这个功能了。可以参考我们这篇文章:TypeScript使用枚举封装和装饰器优雅的定义字典。今天我们就不赘述之前的设计了,可以先阅读之后继续下面的内容。

3.1 封装枚举类

export class DateTimeFormatter extends AirEnum {
  static readonly FULL_DATETIME = new DateTimeFormatter(
    'yyyy-MM-dd HH:mm:ss', '年-月-日 时:分:秒'
  )
  static readonly FULL_DATE = new DateTimeFormatter(
    'yyyy-MM-dd', '年-月-日'
  )
  static readonly FULL_TIME = new DateTimeFormatter(
    'HH:mm:ss', '时-分-秒'
  )
}

我们的枚举就声明好了,当然这里的 例子🌰 其实是多余的,因为 DateTimeFormatter 这种几乎好像大概应该也许是没有什么符合字典业务场景的。

当然,为了举例,你就假设它有。

3.2 添加方法

接下来,我们也同样实现这两个方法:

export class DateTimeFormatter extends AirEnum<string> {
  // 一些静态枚举项目

  /**
   * 格式化毫秒时间戳
   * @param timestamp 毫秒时间戳
   */
  format(timestamp: number) {
    return AirDateTime.formatFromMilliSecond(timestamp)
  }

  /**
   * 格式化当前时间
   */
  formatCurrent() {
    return this.format(Date.now().valueOf())
  }
}

如上,我们实现了和 Java 一样的方法,然后就可以在调用的时候使用这个方法了:

3.3 调用示例

QQ_1744357214249.png

四、总结

本文通过类的封装,来实现了枚举功能以及 枚举形参倒置 的功能,虽然大概可能没什么用。

但我还是建议你先读 TypeScript使用枚举封装和装饰器优雅的定义字典 这个文章。

今天周五,祝大家双休。

Bye。

TypeScript深度强化第一天

作者 旧时光_
2025年6月26日 16:29

TypeScript深度强化第一天

从JavaScript到TypeScript的蜕变之旅,掌握静态类型系统的精髓

🎯 学习目标

  • 理解TypeScript的核心价值和静态类型检查机制
  • 掌握TypeScript的基础类型系统
  • 学会使用联合类型、字面量类型等高级特性
  • 熟练运用函数类型注解和对象类型定义
  • 了解类型缩小和类型断言的使用场景

1. TypeScript的静态类型革命

1.1 为什么需要TypeScript?

动态类型语言的痛点

JavaScript作为动态类型语言,虽然灵活性很强,但在大型项目中容易出现类型错误。这些错误往往要在运行时才能被发现,给开发和维护带来了巨大的挑战:

  • 运行时错误:类型不匹配的错误只有在代码执行时才会暴露
  • 拼写错误难发现:属性名或方法名的拼写错误很难被及时发现
  • 重构困难:修改接口或数据结构时,很难确保所有使用的地方都被正确更新
  • 代码可读性差:缺少类型信息,代码的意图不够明确
  • 团队协作困难:不同开发者对数据结构的理解可能存在偏差
// JavaScript中常见的运行时错误
function calculatePrice(item) {
    return item.price * item.quantity;
}

const product = {
    name: "MacBook Pro",
    price: 2999,
    // 忘记添加 quantity 属性
};

console.log(calculatePrice(product)); // NaN - 运行时才发现错误

// 更多常见的JavaScript问题
function processUser(user) {
    console.log(user.toUpperCase()); // 如果user不是string,运行时报错
}

// 拼写错误
const user = { name: "张三", age: 25 };
console.log(user.nam); // undefined,很难发现拼写错误

TypeScript的解决方案

TypeScript通过引入静态类型系统,在编译时就能发现这类问题。它保留了JavaScript的灵活性,同时添加了可选的类型注解,让代码更加安全、可读和易于维护:

interface Product {
    name: string;
    price: number;
    quantity: number;
}

function calculatePrice(item: Product): number {
    return item.price * item.quantity;
}

const product: Product = {
    name: "MacBook Pro",
    price: 2999,
    // TypeScript会提示缺少quantity属性
};

// TypeScript能捕获的错误类型
interface User {
    name: string;
    age: number;
}

function processUser(user: User): void {
    // TypeScript会阻止这种错误
    // console.log(user.toUpperCase()); // 错误:User类型没有toUpperCase方法
    console.log(user.name.toUpperCase()); // 正确
}

1.2 静态类型检查的优势

1.2.1 编译时错误检测

提前发现问题,避免运行时错误

TypeScript最大的优势之一就是能在编译阶段发现潜在的错误,而不需要等到代码运行时。这种"左移测试"的理念大大提高了代码质量和开发效率:

  • 参数类型检查:确保函数调用时传入正确类型的参数
  • 返回值类型验证:保证函数返回值符合预期类型
  • 方法存在性检查:防止调用不存在的方法或属性
  • 拼写错误检测:自动发现属性名或方法名的拼写错误
// 类型安全的函数调用
function formatUserInfo(name: string, age: number): string {
    return `用户:${name},年龄:${age}岁`;
}

// 正确的调用
console.log(formatUserInfo("张三", 25));

// TypeScript会在编译时阻止错误的调用
// formatUserInfo(25, "张三"); // 参数类型错误
// formatUserInfo("李四"); // 参数数量不匹配

// 方法调用错误检测
const text = "Hello World";
// console.log(text.toLowercase()); // 错误:方法名拼写错误
console.log(text.toLowerCase()); // 正确
1.2.2 更好的代码智能提示

智能感知让开发更高效

TypeScript的类型信息为IDE提供了丰富的上下文,使得代码补全、重构和导航功能更加强大:

  • 精确的自动补全:IDE能准确提示对象的属性和方法
  • 参数提示:显示函数参数的类型和说明
  • 快速文档:悬停显示类型信息和文档
  • 重构支持:安全地重命名变量、函数和属性
  • 导航功能:快速跳转到类型定义和实现
interface DatabaseUser {
    id: number;
    username: string;
    email: string;
    createdAt: Date;
    profile: {
        firstName: string;
        lastName: string;
        avatar?: string;
    };
}

function displayUserInfo(user: DatabaseUser): void {
    // IDE会提供精确的代码补全
    console.log(user.profile.firstName); // 自动提示profile下的属性
    console.log(user.createdAt.getFullYear()); // 自动提示Date类型的方法
}
1.2.3 重构安全性

大型项目重构的安全保障

在大型项目中,代码重构是家常便饭。TypeScript的类型系统为重构提供了强大的安全保障:

  • 接口变更检测:修改接口时,所有使用该接口的地方都会被标记
  • 依赖关系追踪:清晰地了解代码之间的依赖关系
  • 批量重命名:可以安全地重命名类型、变量和函数
  • 影响范围分析:快速了解修改的影响范围
  • 向后兼容性检查:确保API变更不会破坏现有代码
// 当我们修改接口定义时,TypeScript会帮助我们找到所有需要更新的地方
interface ApiResponse {
    success: boolean;
    data: any;
    // 添加新字段时,TypeScript会提示哪些地方需要处理
    errorCode?: string;
}

function handleResponse(response: ApiResponse): void {
    if (response.success) {
        console.log("成功:", response.data);
    } else {
        // 如果我们添加了errorCode字段,这里可能需要更新
        console.log("失败:", response.errorCode || "未知错误");
    }
}

1.3 TypeScript的编译过程

1.3.1 类型擦除(Type Erasure)

类型信息在运行时完全消失

TypeScript的一个重要特性是类型擦除。所有的类型注解、接口定义、类型别名等在编译成JavaScript后都会被完全移除,不会影响运行时性能:

  • 零运行时开销:类型信息不会增加最终代码的体积
  • 纯JavaScript输出:编译结果是标准的JavaScript代码
  • 类型安全保证:在开发阶段提供类型安全,运行时保持JavaScript的性能
  • 渐进式采用:可以逐步为现有JavaScript项目添加类型
// TypeScript源码
function greet(name: string): string {
    return `Hello, ${name}!`;
}

// 编译后的JavaScript
function greet(name) {
    return `Hello, ${name}!`;
}

// 类型注解在运行时完全消失
interface User {
    name: string;
    age: number;
}

// 编译后接口定义完全消失,不会产生任何JavaScript代码
1.3.2 编译目标和降级

兼容不同JavaScript版本的能力

TypeScript不仅提供类型检查,还能将现代JavaScript语法转换为兼容旧版本的代码:

  • 目标版本选择:可以选择编译到ES3、ES5、ES2015等不同版本
  • 语法转换:将新语法转换为兼容的旧语法
  • Polyfill支持:为缺失的API提供兼容性实现
  • 浏览器兼容性:确保代码能在目标浏览器中正常运行
// 现代TypeScript代码
const users = ["Alice", "Bob", "Charlie"];
const upperCaseNames = users.map(name => name.toUpperCase());

// 可选链操作符
const user = { profile: { name: "张三" } };
console.log(user.profile?.name);

// 编译为ES5时的输出
var users = ["Alice", "Bob", "Charlie"];
var upperCaseNames = users.map(function(name) {
    return name.toUpperCase();
});

// 可选链会被转换为安全的属性访问
var _a;
console.log((_a = user.profile) === null || _a === void 0 ? void 0 : _a.name);

2. TypeScript基础类型深度解析

类型系统是TypeScript的核心

TypeScript的类型系统建立在JavaScript的基础上,为每种JavaScript值提供了对应的类型定义。理解和掌握这些基础类型是学习TypeScript的第一步,也是最重要的一步。

2.1 原始类型的正确使用

原始类型是构建复杂类型的基石

JavaScript有7种原始类型:string、number、boolean、null、undefined、symbol和bigint。需要注意的是,function和object都不是原始类型,它们属于引用类型(对象类型)。原始类型的值是不可变的,直接存储在栈内存中,而引用类型的值是可变的,存储在堆内存中。TypeScript为这些原始类型提供了对应的类型注解,让我们能够明确地声明变量的类型。

2.1.1 string类型

字符串类型的完整特性

string类型是最常用的原始类型之一,用于表示文本数据。TypeScript对字符串类型提供了完整的支持:

  • 多种字符串格式:支持单引号、双引号和模板字符串
  • 类型安全的方法调用:所有字符串方法都有准确的类型定义
  • 模板字符串类型推断:能够推断模板字符串的结果类型
  • 包装类型避免:区分原始string类型和String对象类型
// 基础字符串类型
let userName: string = "TypeScript学习者";
let message: string = '单引号字符串';
let template: string = `模板字符串,用户:${userName}`;

// 字符串的方法调用是类型安全的
console.log(userName.length); // TypeScript知道string有length属性
console.log(userName.toUpperCase()); // 自动提示string的方法

// 避免使用包装类型
// let wrongName: String = "错误示例"; // String是对象类型,不是原始类型
let correctName: string = "正确示例"; // 使用小写的string
2.1.2 number类型

数字类型的丰富表示方式

JavaScript中的number类型比其他语言更加灵活,TypeScript完全继承了这种特性:

  • 统一的数字类型:整数和浮点数都使用number类型
  • 多进制表示:支持十进制、十六进制、二进制、八进制
  • 特殊数值处理:Infinity、-Infinity、NaN的类型安全处理
  • BigInt支持:大整数类型的完整支持
  • 精度考虑:理解JavaScript数字精度限制
// 数字类型支持各种进制
let decimal: number = 42;
let hex: number = 0x2A;
let binary: number = 0b101010;
let octal: number = 0o52;

// 特殊数值
let infinity: number = Infinity;
let negativeInfinity: number = -Infinity;
let notANumber: number = NaN;

// 数字方法的类型安全调用
let price = 19.99;
console.log(price.toFixed(2)); // "19.99"
console.log(price.toString()); // "19.99"

// BigInt类型(ES2020+)
let bigNumber: bigint = 9007199254740991n;
let anotherBig: bigint = BigInt(9007199254740991);
2.1.3 boolean类型

布尔类型的严格性

boolean类型只有两个值:true和false。TypeScript对布尔类型的处理比JavaScript更加严格:

  • 严格的布尔值:只接受true和false,不允许truthy/falsy值
  • 类型安全的逻辑运算:逻辑运算符的结果类型推断
  • 避免隐式转换:防止意外的类型转换导致的错误
  • 条件判断优化:在条件语句中的类型缩小
// 布尔类型
let isActive: boolean = true;
let isCompleted: boolean = false;

// 布尔值的逻辑运算
let canAccess: boolean = isActive && !isCompleted;

// 避免隐式类型转换混淆
function checkStatus(active: boolean): string {
    // 明确的布尔值比较
    if (active === true) {
        return "激活状态";
    }
    return "非激活状态";
}

// 错误示例:不要将其他类型当作boolean使用
// checkStatus(1); // 错误:number不能赋值给boolean
// checkStatus("true"); // 错误:string不能赋值给boolean

2.2 类型推断机制

智能的类型推断减少冗余注解

TypeScript具有强大的类型推断能力,能够在很多情况下自动推断出变量的类型,无需显式声明:

  • 初始值推断:根据变量的初始值推断类型
  • 上下文推断:根据使用上下文推断类型
  • 最佳公共类型:数组和联合类型的推断
  • 控制流分析:在条件分支中的类型推断
  • 函数返回值推断:根据return语句推断函数返回类型
// TypeScript的类型推断
let inferredString = "TypeScript"; // 推断为string类型
let inferredNumber = 42; // 推断为number类型
let inferredBoolean = true; // 推断为boolean类型

// 复杂的类型推断
let user = {
    name: "张三",  // string
    age: 25,      // number
    active: true  // boolean
}; // 推断为 { name: string; age: number; active: boolean; }

// 函数返回类型推断
function createUser(name: string, age: number) {
    return { name, age, id: Math.random() };
} // 返回类型被推断为 { name: string; age: number; id: number; }

// 最佳通用类型推断
let numbers = [1, 2, 3]; // 推断为number[]
let mixed = [1, "hello", true]; // 推断为(string | number | boolean)[]

2.3 数组类型的多种写法

数组是最常用的复合类型

数组在JavaScript开发中无处不在,TypeScript为数组提供了丰富的类型定义方式,确保数组操作的类型安全。

2.3.1 数组类型声明

两种等价的数组类型声明语法

TypeScript提供了两种声明数组类型的语法,虽然功能相同,但在不同场景下有不同的推荐用法:

  • T[]语法:简洁明了,推荐在大多数情况下使用
  • Array语法:在复杂泛型场景下更清晰
  • 性能考虑:两种写法在编译后完全相同,无性能差异
// 数组类型的两种声明方式
let fruits: string[] = ["苹果", "香蕉", "橙子"];
let numbers: Array<number> = [1, 2, 3, 4, 5];

// 两种写法完全等价,但推荐使用第一种(更简洁)
let userNames: string[] = ["Alice", "Bob"];
let userAges: Array<number> = [25, 30]; // 也可以,但不如上面简洁
2.3.2 复杂数组类型

处理复杂数据结构的数组

在实际开发中,数组经常包含复杂的数据结构,TypeScript能够为这些复杂场景提供完整的类型支持:

  • 对象数组:最常见的复杂数组类型
  • 嵌套数组:多维数组的类型定义
  • 函数数组:存储函数的数组类型安全
  • 接口约束:使用接口定义数组元素结构
// 对象数组
interface User {
    id: number;
    name: string;
}

let users: User[] = [
    { id: 1, name: "张三" },
    { id: 2, name: "李四" }
];

// 嵌套数组
let matrix: number[][] = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];

// 函数数组
type HandlerFunction = (data: string) => void;
let handlers: HandlerFunction[] = [
    (data) => console.log(`处理1: ${data}`),
    (data) => console.log(`处理2: ${data}`)
];
2.3.3 联合类型数组

支持多种类型的灵活数组

现实世界的数据往往不是单一类型,TypeScript的联合类型数组完美解决了这个问题:

  • 基础联合类型:(string | number)[]允许字符串和数字混合
  • 对象联合类型:使用discriminated union确保类型安全
  • 条件类型检查:运行时判断具体类型
  • 类型守卫应用:安全地访问联合类型的属性
// 混合类型数组使用联合类型
let mixedArray: (string | number)[] = ["TypeScript", 2024, "学习"];

// 更复杂的联合类型数组
type Status = "pending" | "success" | "error";
let statusList: Status[] = ["pending", "success", "error"];

// 对象联合类型数组
interface Dog {
    type: "dog";
    breed: string;
}

interface Cat {
    type: "cat";
    color: string;
}

let pets: (Dog | Cat)[] = [
    { type: "dog", breed: "金毛" },
    { type: "cat", color: "橘色" }
];
2.3.4 只读数组和元组

不可变数据结构的类型安全

函数式编程和不可变数据结构越来越重要,TypeScript提供了强大的只读数组和元组支持:

  • 只读数组:防止意外修改数组内容
  • 元组类型:固定长度和类型的数组,适合返回多个值
  • 可选元组元素:灵活的元组定义
  • 剩余元素:处理可变长度的结构化数据
  • 不可变性保证:编译时防止修改操作
// 只读数组
let readonlyFruits: readonly string[] = ["苹果", "香蕉"];
// readonlyFruits.push("橙子"); // 错误:只读数组不能修改
// readonlyFruits[0] = "葡萄"; // 错误:不能修改元素

// ReadonlyArray<T> 类型
let readonlyNumbers: ReadonlyArray<number> = [1, 2, 3, 4, 5];

// 元组类型(固定长度和类型的数组)
let coordinates: [number, number] = [10, 20];
let userInfo: [string, number, boolean] = ["张三", 25, true];

// 可选元组元素
let optionalTuple: [string, number?] = ["hello"]; // 第二个元素可选

// 剩余元素元组
let restTuple: [string, ...number[]] = ["标签", 1, 2, 3, 4];

// 只读元组
let readonlyTuple: readonly [string, number] = ["坐标", 100];
2.3.5 数组方法的类型安全

原生数组方法的完整类型支持

TypeScript为所有原生数组方法提供了精确的类型定义,确保链式调用的类型安全:

  • map方法:转换元素类型,返回新类型的数组
  • filter方法:过滤元素,保持原类型但可能返回undefined
  • reduce方法:复杂的累积操作类型推断
  • 链式调用:多个方法组合时的类型流转
  • 类型收窄:在回调函数中的类型细化
let numbers: number[] = [1, 2, 3, 4, 5];

// TypeScript能确保数组方法的类型安全
let doubled = numbers.map(n => n * 2); // 返回类型为number[]
let filtered = numbers.filter(n => n > 2); // 返回类型为number[]
let found = numbers.find(n => n > 3); // 返回类型为number | undefined

// 复杂的链式调用
let result = numbers
    .filter(n => n % 2 === 0)  // number[]
    .map(n => n.toString())    // string[]
    .join(", ");               // string

// reduce方法的类型推断
let sum = numbers.reduce((acc, curr) => acc + curr, 0); // number
let concatenated = numbers.reduce((acc, curr) => acc + curr.toString(), ""); // string

2.4 any、unknown、never类型详解

处理动态和特殊情况的类型

在TypeScript的类型系统中,有三个特殊的类型用于处理动态内容和边界情况:any、unknown和never。理解它们的区别和使用场景对于编写安全的TypeScript代码至关重要。

2.4.1 any类型的使用场景

灵活性与安全性的权衡

any类型是TypeScript提供的"逃生舱",它关闭了类型检查,应该谨慎使用:

  • 合理使用场景:第三方库迁移、动态内容处理、临时解决方案
  • 潜在危险:失去类型安全、智能提示缺失、运行时错误
  • 最佳实践:尽量避免使用,考虑unknown作为替代
  • 逐步迁移:在JavaScript项目向TypeScript迁移时的临时方案
// any类型的合理使用场景
function parseApiResponse(response: any): void {
    // 来自第三方API的数据,类型未知
    console.log(response.data);
    console.log(response.status);
}

// 逐步迁移JavaScript代码时的临时方案
let legacyData: any = getLegacyData();

// 动态内容(真正类型无法确定)
function handleDynamicContent(content: any): void {
    // 处理用户上传的各种格式文件
    if (typeof content === 'string') {
        console.log(content.length);
    } else if (Array.isArray(content)) {
        console.log(content.length);
    }
}

// any类型的危险性
let dangerousAny: any = "hello";
dangerousAny.toUpperCase(); // 运行时正常
dangerousAny = 42;
dangerousAny.toUpperCase(); // 运行时错误!TypeScript不会警告
2.4.2 unknown类型:安全的any

类型安全的顶级类型

unknown是TypeScript 3.0引入的安全版any,它保持了灵活性的同时强制进行类型检查:

  • 安全的顶级类型:任何值都可以赋给unknown,但使用前必须检查类型
  • 强制类型检查:防止直接访问未知类型的属性和方法
  • 类型收窄:通过类型守卫安全地使用unknown值
  • API设计:适合用于接收外部数据的函数参数类型
// unknown类型更安全
let safeUnknown: unknown = "hello";

// 必须进行类型检查才能使用
if (typeof safeUnknown === "string") {
    console.log(safeUnknown.toUpperCase()); // 类型安全
}

// unknown类型的实际应用
function parseJson(jsonString: string): unknown {
    return JSON.parse(jsonString);
}

const result = parseJson('{"name": "张三", "age": 25}');

// 需要类型断言或类型守卫
if (typeof result === "object" && result !== null && "name" in result) {
    console.log((result as { name: string }).name);
}

// 更好的做法:使用类型守卫
function isUser(obj: unknown): obj is { name: string; age: number } {
    return typeof obj === "object" && 
           obj !== null && 
           "name" in obj && 
           "age" in obj &&
           typeof (obj as any).name === "string" &&
           typeof (obj as any).age === "number";
}

if (isUser(result)) {
    console.log(result.name); // 类型安全
    console.log(result.age);
}
2.4.3 never类型:永不存在的值

表示永远不会发生的类型

never类型是TypeScript类型系统中的底部类型,表示永远不会出现的值。它在类型安全和详尽性检查中发挥着重要作用:

  • 抛出异常的函数:永远不会正常返回的函数
  • 无限循环:永远不会结束的函数
  • 详尽性检查:确保switch语句处理了所有可能的情况
  • 类型收窄:在条件语句中排除不可能的情况
  • 联合类型过滤:从联合类型中移除某些类型
// never类型表示永不会发生的类型
function throwError(message: string): never {
    throw new Error(message);
    // 这个函数永远不会正常返回
}

function infiniteLoop(): never {
    while (true) {
        console.log("永远循环");
    }
    // 这个函数永远不会结束
}

// never在联合类型中的应用
type Status = "loading" | "success" | "error";

function handleStatus(status: Status): string {
    switch (status) {
        case "loading":
            return "加载中...";
        case "success":
            return "成功";
        case "error":
            return "错误";
        default:
            // 这里status的类型是never
            // 确保我们处理了所有可能的情况
            const exhaustiveCheck: never = status;
            return exhaustiveCheck;
    }
}

// 利用never进行详尽性检查
type Shape = "circle" | "square" | "triangle";

function getArea(shape: Shape): number {
    switch (shape) {
        case "circle":
            return Math.PI * 5 * 5;
        case "square":
            return 5 * 5;
        // 如果我们忘记处理triangle,TypeScript会报错
        // case "triangle":
        //     return 0.5 * 5 * 5;
        default:
            const _exhaustiveCheck: never = shape; // 类型错误!
            return _exhaustiveCheck;
    }
}
2.4.4 void类型和undefined

表示无返回值和未定义值的区别

void和undefined都与"没有值"相关,但它们在TypeScript中有着不同的语义和用途:

  • void类型:表示函数没有返回值,常用于事件处理器和副作用函数
  • undefined类型:表示变量可能没有被赋值,或者函数显式返回undefined
  • 使用场景:void用于函数返回类型,undefined用于变量类型
  • 赋值规则:void函数可以返回undefined,但undefined函数必须显式返回
  • 类型兼容性:在非严格模式下,undefined可以赋值给void
// void类型:表示没有返回值
function logMessage(message: string): void {
    console.log(message);
    // 可以不显式返回任何值
}

function logMessageExplicit(message: string): void {
    console.log(message);
    return; // 可以显式返回undefined
}

// void与undefined的区别
function returnsVoid(): void {
    console.log("无返回值");
}

function returnsUndefined(): undefined {
    console.log("返回undefined");
    return undefined; // 必须显式返回undefined
}

// void类型的实际应用
type EventHandler = (event: Event) => void;

const button = document.createElement("button");
button.addEventListener("click", (event): void => {
    console.log("按钮被点击");
    // 不需要返回值
});
2.4.5 object、Object和{}的区别

三种对象类型的细微差别

TypeScript中有三种看似相似但实际不同的对象类型,理解它们的区别对于编写正确的类型定义很重要:

  • object类型:表示所有非原始类型,是最严格的对象类型定义
  • Object类型:表示JavaScript的Object类型,所有值都可以赋给它
  • {}类型:表示空对象类型,但实际上几乎所有值都可以赋给它
  • 实际使用:推荐使用具体的接口或类型别名而不是这些通用类型
  • 类型安全性:object > {} > Object(按安全性递减)
// object类型:表示非原始类型
let obj1: object = { name: "张三" };
let obj2: object = [1, 2, 3];
let obj3: object = new Date();
// let obj4: object = "string"; // 错误:string是原始类型

// Object类型:所有值都可以赋值给Object
let obj5: Object = "string"; // 可以
let obj6: Object = 42; // 可以
let obj7: Object = true; // 可以

// {}类型:空对象类型,也是所有值都可以赋值
let obj8: {} = "string"; // 可以
let obj9: {} = 42; // 可以
// let obj10: {} = null; // 错误(在严格模式下)

// 推荐的做法
interface User {
    name: string;
    age: number;
}

let user: User = { name: "张三", age: 25 }; // 明确的接口类型

3. 函数类型的艺术

函数是TypeScript中最复杂的类型概念

函数在JavaScript中是一等公民,TypeScript为函数提供了极其丰富的类型支持。从简单的参数类型到复杂的高阶函数,从函数重载到泛型函数,TypeScript的函数类型系统让我们能够构建更加安全和可维护的代码。

3.1 函数参数和返回值类型

函数签名是契约的体现

函数签名定义了函数的输入和输出,是调用者和实现者之间的契约。TypeScript通过类型注解让这个契约变得显式和可验证。

3.1.1 基础函数类型注解

清晰的参数和返回值类型定义

函数类型注解包括参数类型和返回值类型,这是TypeScript函数类型的基础:

  • 参数类型注解:明确每个参数的预期类型
  • 返回值类型注解:声明函数的返回类型
  • 类型推断:TypeScript能够推断返回值类型,但显式声明更清晰
  • void类型:表示函数不返回任何有意义的值
// 基础函数类型注解
function greetUser(name: string, greeting: string = "你好"): string {
    return `${greeting}, ${name}!`;
}

// 箭头函数的类型注解
const calculateArea = (width: number, height: number): number => {
    return width * height;
};

// 函数表达式
const multiply = function(a: number, b: number): number {
    return a * b;
};
3.1.2 参数类型详解

灵活而严格的参数类型系统

TypeScript提供了丰富的参数类型选项,让函数能够适应不同的调用场景:

  • 必需参数:调用时必须提供的参数
  • 可选参数:使用?修饰符,可以不传递的参数
  • 默认参数:有默认值的参数,类型会从默认值推断
  • 剩余参数:使用...收集多个参数到数组中
  • 参数顺序:可选参数必须在必需参数之后
// 必需参数
function processOrder(orderId: string, customerId: number): void {
    console.log(`处理订单 ${orderId},客户 ${customerId}`);
}

// 可选参数(必须在必需参数之后)
function createUser(name: string, email?: string, age?: number): object {
    const user: any = { name };
    if (email) user.email = email;
    if (age) user.age = age;
    return user;
}

// 默认参数
function formatMessage(message: string, prefix: string = "[INFO]"): string {
    return `${prefix} ${message}`;
}

// 剩余参数
function sum(...numbers: number[]): number {
    return numbers.reduce((total, num) => total + num, 0);
}

console.log(sum(1, 2, 3, 4, 5)); // 15

// 剩余参数的复杂用法
function logMessages(prefix: string, ...messages: string[]): void {
    messages.forEach(msg => console.log(`${prefix}: ${msg}`));
}

logMessages("DEBUG", "消息1", "消息2", "消息3");
3.1.3 函数重载

一个函数名,多种调用方式

函数重载允许同一个函数根据不同的参数类型和数量有不同的行为,这在处理多种输入格式时非常有用:

  • 重载声明:定义函数的多个签名
  • 实现签名:实际的函数实现,必须兼容所有重载
  • 类型选择:TypeScript根据参数自动选择匹配的重载
  • 返回类型精确:每个重载可以有精确的返回类型
  • 实际应用:API函数、工具函数的多态处理
// 函数重载声明
function processInput(input: string): string;
function processInput(input: number): number;
function processInput(input: boolean): boolean;

// 函数实现(必须兼容所有重载签名)
function processInput(input: string | number | boolean): string | number | boolean {
    if (typeof input === "string") {
        return input.toUpperCase();
    }
    if (typeof input === "number") {
        return input * 2;
    }
    return !input;
}

// 使用时TypeScript会根据参数类型选择正确的重载
let result1 = processInput("hello"); // string
let result2 = processInput(42); // number
let result3 = processInput(true); // boolean

// 更复杂的函数重载示例
interface User {
    id: number;
    name: string;
}

function findUser(id: number): User | undefined;
function findUser(name: string): User[] | undefined;
function findUser(criteria: number | string): User | User[] | undefined {
    if (typeof criteria === "number") {
        // 根据ID查找单个用户
        return { id: criteria, name: "测试用户" };
    } else {
        // 根据名称查找多个用户
        return [{ id: 1, name: criteria }];
    }
}

3.2 函数类型表达式和高阶函数

函数作为值的类型定义

在JavaScript中,函数是一等公民,可以作为值传递、存储和操作。TypeScript为这种函数式编程模式提供了完整的类型支持。

3.2.1 函数类型别名

复用函数签名的优雅方式

函数类型别名让我们能够为函数签名创建可复用的类型定义:

  • 类型别名:使用type关键字定义函数类型
  • 签名复用:相同的函数签名可以在多处使用
  • 语义化:给函数类型起有意义的名字
  • 维护性:修改函数签名时只需要在一个地方修改
// 函数类型别名
type CalculatorFunction = (a: number, b: number) => number;

const add: CalculatorFunction = (x, y) => x + y;
const subtract: CalculatorFunction = (x, y) => x - y;
const multiply: CalculatorFunction = (x, y) => x * y;

// 使用函数类型别名
function calculate(op: CalculatorFunction, a: number, b: number): number {
    return op(a, b);
}

console.log(calculate(add, 10, 5)); // 15
console.log(calculate(multiply, 3, 4)); // 12
3.2.2 复杂函数类型

处理异步和回调的类型定义

现代JavaScript大量使用异步操作和回调函数,TypeScript为这些复杂场景提供了强大的类型支持:

  • 回调函数类型:定义回调函数的参数和返回值
  • 泛型函数类型:使用泛型创建灵活的函数类型
  • 异步操作类型:处理Promise、回调等异步模式
  • 错误处理:在类型层面处理成功和失败情况
// 回调函数类型
type EventCallback<T> = (data: T) => void;
type ErrorCallback = (error: Error) => void;

// 异步操作函数类型
type AsyncOperation<T> = (
    onSuccess: EventCallback<T>,
    onError: ErrorCallback
) => void;

// 使用复杂函数类型
const fetchUserData: AsyncOperation<User> = (onSuccess, onError) => {
    setTimeout(() => {
        try {
            const user = { id: 1, name: "张三" };
            onSuccess(user);
        } catch (error) {
            onError(error as Error);
        }
    }, 1000);
};

// 调用
fetchUserData(
    (user) => console.log("用户数据:", user),
    (error) => console.error("错误:", error.message)
);
3.2.3 高阶函数模式

函数式编程的类型安全实现

高阶函数是函数式编程的核心概念,TypeScript让这些模式变得类型安全:

  • 函数工厂:返回函数的函数,创建特定行为的函数
  • 函数组合:将多个函数组合成一个新函数
  • 柯里化:将多参数函数转换为单参数函数序列
  • 装饰器模式:为现有函数添加额外功能
  • 闭包类型安全:确保闭包中变量的类型安全
// 高阶函数:返回函数的函数
function createCounter(initialValue: number): () => number {
    let count = initialValue;
    return () => ++count;
}

const counter = createCounter(0);
console.log(counter()); // 1
console.log(counter()); // 2

// 更复杂的高阶函数:函数工厂
type ValidationRule<T> = (value: T) => boolean;

function createValidator<T>(rules: ValidationRule<T>[]): ValidationRule<T> {
    return (value: T) => {
        return rules.every(rule => rule(value));
    };
}

// 创建验证规则
const isNotEmpty: ValidationRule<string> = (value) => value.length > 0;
const isLongEnough: ValidationRule<string> = (value) => value.length >= 6;
const hasNumbers: ValidationRule<string> = (value) => /\d/.test(value);

// 组合验证器
const passwordValidator = createValidator([isNotEmpty, isLongEnough, hasNumbers]);

console.log(passwordValidator("123456")); // true
console.log(passwordValidator("abc")); // false

// 柯里化函数
type CurriedFunction<A, B, C> = (a: A) => (b: B) => C;

const curriedAdd: CurriedFunction<number, number, number> = (a) => (b) => a + b;

const add5 = curriedAdd(5);
console.log(add5(3)); // 8

// 函数组合
type UnaryFunction<T, R> = (arg: T) => R;

function compose<A, B, C>(
    f: UnaryFunction<B, C>,
    g: UnaryFunction<A, B>
): UnaryFunction<A, C> {
    return (x: A) => f(g(x));
}

const addOne = (x: number) => x + 1;
const double = (x: number) => x * 2;

const addOneThenDouble = compose(double, addOne);
console.log(addOneThenDouble(3)); // 8 (3 + 1 = 4, 4 * 2 = 8)
3.2.4 装饰器模式函数

为函数增强功能的类型安全方式

装饰器模式允许我们在不修改原函数的情况下为其添加额外功能,TypeScript确保这个过程是类型安全的:

  • 函数装饰器:包装原函数,添加额外行为
  • 性能监控:添加计时功能,监控函数执行时间
  • 缓存机制:为纯函数添加结果缓存
  • 日志记录:自动记录函数调用和结果
  • 类型保持:装饰后的函数保持原有的类型签名
// 函数装饰器
type DecoratedFunction<T extends (...args: any[]) => any> = T;

function timing<T extends (...args: any[]) => any>(fn: T): T {
    return ((...args: any[]) => {
        console.time('执行时间');
        const result = fn(...args);
        console.timeEnd('执行时间');
        return result;
    }) as T;
}

function cache<T extends (...args: any[]) => any>(fn: T): T {
    const cacheMap = new Map();
    
    return ((...args: any[]) => {
        const key = JSON.stringify(args);
        if (cacheMap.has(key)) {
            console.log('从缓存返回');
            return cacheMap.get(key);
        }
        
        const result = fn(...args);
        cacheMap.set(key, result);
        return result;
    }) as T;
}

// 使用装饰器
const expensiveCalculation = timing(cache((n: number): number => {
    // 模拟耗时计算
    let result = 0;
    for (let i = 0; i < n * 1000000; i++) {
        result += i;
    }
    return result;
}));

console.log(expensiveCalculation(100)); // 第一次计算,显示执行时间
console.log(expensiveCalculation(100)); // 从缓存返回,很快

3.3 上下文类型推断和this类型

智能的类型推断让代码更简洁

TypeScript的类型推断不仅仅基于变量的值,还会考虑使用的上下文环境,这让我们能够编写更少类型注解的代码。

3.3.1 上下文类型推断

根据使用环境自动推断类型

上下文类型推断是TypeScript最智能的特性之一,它能根据函数的使用环境自动推断参数类型:

  • 数组方法推断:map、filter、forEach等方法的回调函数参数类型自动推断
  • 事件处理器推断:DOM事件监听器的事件参数类型自动推断
  • Promise链推断:then方法的回调函数参数类型自动推断
  • 函数参数推断:高阶函数的回调参数类型自动推断
  • 减少冗余:避免重复的类型注解,让代码更简洁
// TypeScript可以从上下文推断函数参数类型
const userNames = ["Alice", "Bob", "Charlie"];

// 参数item会被推断为string类型
userNames.forEach((item) => {
    console.log(item.toUpperCase()); // TypeScript知道item是string
});

// map方法的上下文类型推断
const userAges = [25, 30, 35];
const ageDescriptions = userAges.map((age) => {
    return `${age}岁`; // age被推断为number类型
});

// 事件处理器的上下文类型推断
const button = document.querySelector('button');
button?.addEventListener('click', (event) => {
    // event参数自动推断为MouseEvent类型
    console.log(event.clientX, event.clientY);
});

// Promise的上下文类型推断
fetch('/api/users')
    .then((response) => {
        // response自动推断为Response类型
        return response.json();
    })
    .then((data) => {
        // data推断为any类型,因为json()返回Promise<any>
        console.log(data);
    });
3.3.2 this类型详解

TypeScript中this的类型安全处理

JavaScript中的this一直是令人困惑的概念,TypeScript通过类型系统让this的使用变得安全和可预测:

  • 显式this参数:在函数签名中明确this的类型
  • this返回类型:支持链式调用的类型安全
  • 方法绑定检查:确保方法在正确的上下文中调用
  • 类型收窄:在不同上下文中this类型的自动收窄
  • ThisType工具类型:高级this类型操作
// 函数中的this类型
interface Calculator {
    value: number;
    add(this: Calculator, num: number): Calculator;
    multiply(this: Calculator, num: number): Calculator;
    getValue(this: Calculator): number;
}

const calculator: Calculator = {
    value: 0,
    add(num: number) {
        this.value += num;
        return this; // 支持链式调用
    },
    multiply(num: number) {
        this.value *= num;
        return this;
    },
    getValue() {
        return this.value;
    }
};

// 链式调用
const result = calculator.add(5).multiply(2).getValue(); // 10

// this类型在类中的应用
class FluentAPI {
    private data: string[] = [];

    add(item: string): this {
        this.data.push(item);
        return this;
    }

    remove(item: string): this {
        const index = this.data.indexOf(item);
        if (index > -1) {
            this.data.splice(index, 1);
        }
        return this;
    }

    clear(): this {
        this.data = [];
        return this;
    }

    getAll(): string[] {
        return [...this.data];
    }
}

const api = new FluentAPI();
const items = api.add("item1").add("item2").remove("item1").getAll();

// ThisType工具类型
interface APIContext {
    request: (url: string) => Promise<any>;
    cache: Map<string, any>;
}

type APIModule = {
    getUser(): Promise<User>;
    getUsers(): Promise<User[]>;
} & ThisType<APIContext>;

const userAPI: APIModule = {
    async getUser() {
        // this被推断为APIContext类型
        return this.request('/api/user');
    },
    async getUsers() {
        const cached = this.cache.get('users');
        if (cached) return cached;
        
        const users = await this.request('/api/users');
        this.cache.set('users', users);
        return users;
    }
};

4. 对象类型的精细控制

对象是JavaScript应用的核心数据结构

在JavaScript应用中,对象是最重要的数据结构。TypeScript为对象提供了极其精细的类型控制能力,从简单的属性类型到复杂的结构约束,让我们能够构建类型安全的数据模型。

4.1 对象类型定义详解

多种方式定义对象的形状

TypeScript提供了多种定义对象类型的方式,每种方式都有其适用场景和优势。

4.1.1 内联对象类型

直接在使用处定义对象结构

内联对象类型是最直接的对象类型定义方式,适合一次性使用的场景:

  • 快速定义:在函数参数或变量声明处直接定义对象结构
  • 嵌套支持:支持深层嵌套的对象结构定义
  • 临时使用:适合不需要复用的一次性对象类型
  • 简单明了:类型定义和使用在同一位置,易于理解
  • 局限性:不能复用,复杂结构会降低可读性
// 内联对象类型
function printUserProfile(user: { name: string; age: number; email?: string }): void {
    console.log(`姓名:${user.name}`);
    console.log(`年龄:${user.age}`);
    if (user.email) {
        console.log(`邮箱:${user.email}`);
    }
}

// 嵌套对象类型
function processOrder(order: {
    id: string;
    customer: {
        name: string;
        address: {
            street: string;
            city: string;
            zipCode: string;
        };
    };
    items: Array<{
        productId: string;
        quantity: number;
        price: number;
    }>;
}): void {
    console.log(`订单 ${order.id} 客户:${order.customer.name}`);
    console.log(`收货地址:${order.customer.address.city}`);
}
4.1.2 类型别名和结构化

构建可复用的类型系统

类型别名是构建大型应用类型系统的基础,它让我们能够创建清晰、可维护的类型定义:

  • 类型复用:定义一次,多处使用,提高代码复用性
  • 语义化命名:给复杂类型起有意义的名字,提高可读性
  • 模块化设计:将大型对象类型分解为小的可组合单元
  • 维护性:修改类型定义时只需要在一个地方修改
  • 组合能力:不同类型可以组合成更复杂的类型结构
// 类型别名提高可读性
type UserProfile = {
    name: string;
    age: number;
    email?: string;
    readonly id: string;
};

// 地址类型
type Address = {
    street: string;
    city: string;
    zipCode: string;
    country?: string;
};

// 客户类型
type Customer = {
    id: string;
    profile: UserProfile;
    address: Address;
    membershipLevel: "bronze" | "silver" | "gold" | "platinum";
};

// 订单项类型
type OrderItem = {
    productId: string;
    productName: string;
    quantity: number;
    unitPrice: number;
    discount?: number;
};

// 订单类型
type Order = {
    readonly id: string;
    customer: Customer;
    items: OrderItem[];
    status: "pending" | "processing" | "shipped" | "delivered" | "cancelled";
    createdAt: Date;
    updatedAt: Date;
};

function createUserProfile(data: Omit<UserProfile, 'id'>): UserProfile {
    return {
        ...data,
        id: Math.random().toString(36).substr(2, 9)
    };
}

// 计算订单总价的复杂示例
function calculateOrderTotal(order: Order): number {
    return order.items.reduce((total, item) => {
        const itemTotal = item.quantity * item.unitPrice;
        const discountAmount = item.discount ? itemTotal * (item.discount / 100) : 0;
        return total + (itemTotal - discountAmount);
    }, 0);
}
4.1.3 只读属性和可选属性

精细控制对象属性的可变性

TypeScript允许我们精确控制对象属性的可变性和可选性,这对于构建安全的数据模型非常重要:

  • 只读属性:使用readonly修饰符防止属性被修改
  • 可选属性:使用?修饰符表示属性可能不存在
  • 不可变设计:构建不可变的数据结构,提高应用的可预测性
  • 部分更新模式:结合可选属性实现灵活的数据更新
  • 深度只读:递归地将所有属性设置为只读
// 只读属性的深入应用
type ImmutableUser = {
    readonly id: string;
    readonly createdAt: Date;
    readonly profile: {
        readonly name: string;
        readonly email: string;
    };
    // 可修改的属性
    lastLoginAt?: Date;
    isActive: boolean;
};

// 深度只读类型
type DeepReadonly<T> = {
    readonly [P in keyof T]: T[P] extends object ? DeepReadonly<T[P]> : T[P];
};

type CompletelyImmutableUser = DeepReadonly<Customer>;

// 可选属性的高级用法
type PartialUpdate<T> = {
    [P in keyof T]?: T[P] extends object ? PartialUpdate<T[P]> : T[P];
};

function updateUser(id: string, updates: PartialUpdate<UserProfile>): UserProfile {
    // 实现用户更新逻辑
    const currentUser = getUserById(id); // 假设的函数
    return {
        ...currentUser,
        ...updates
    };
}

// 示例:只更新部分字段
const updatedUser = updateUser("123", {
    age: 26, // 只更新年龄
    // name 和 email 保持不变
});

4.2 接口vs类型别名深度对比

两种定义对象类型的方式,各有所长

TypeScript提供了接口(interface)和类型别名(type alias)两种定义对象类型的方式。虽然它们在很多场景下可以互换使用,但理解它们的区别和适用场景对于写出优雅的TypeScript代码很重要。

4.2.1 接口的特性

面向对象编程的经典概念

接口是从面向对象编程中借鉴的概念,在TypeScript中有着独特的特性:

  • 继承机制:支持extends关键字进行继承,可以建立类型层次结构
  • 多重继承:一个接口可以继承多个其他接口
  • 声明合并:同名接口会自动合并,这在库的类型扩展中很有用
  • 类实现:类可以使用implements关键字实现接口
  • 开放扩展:便于在不同模块中对同一接口进行扩展
// 接口定义
interface Vehicle {
    brand: string;
    model: string;
    year: number;
}

// 接口可以扩展
interface Car extends Vehicle {
    doors: number;
    fuelType: "gasoline" | "electric" | "hybrid";
}

// 多重继承
interface ElectricVehicle {
    batteryCapacity: number;
    chargingTime: number;
}

interface Autonomous {
    autopilotLevel: 1 | 2 | 3 | 4 | 5;
}

interface ElectricCar extends Car, ElectricVehicle, Autonomous {
    fastChargingSupported: boolean;
}

// 接口可以重新声明(声明合并)
interface Vehicle {
    color?: string; // 这会合并到上面的Vehicle接口中
}

// 现在Vehicle接口包含了所有属性
const myVehicle: Vehicle = {
    brand: "Tesla",
    model: "Model 3",
    year: 2023,
    color: "red"
};
4.2.2 类型别名的特性

更强大的类型计算能力

类型别名提供了比接口更强大的类型表达能力,特别适合复杂的类型操作:

  • 联合类型:可以表示多种可能的类型选择
  • 交集类型:使用&操作符组合多个类型
  • 条件类型:根据条件选择不同的类型
  • 映射类型:基于现有类型生成新类型
  • 模板字面量类型:基于字符串模式生成类型
// 类型别名的交集类型
type Motorcycle = Vehicle & {
    engineSize: number;
    hasWindshield: boolean;
};

// 类型别名可以表示联合类型
type Status = "loading" | "success" | "error";
type ID = string | number;

// 类型别名可以表示条件类型
type NonNullable<T> = T extends null | undefined ? never : T;

// 类型别名可以表示映射类型
type Partial<T> = {
    [P in keyof T]?: T[P];
};

// 复杂的类型操作
type Keys<T> = keyof T;
type Values<T> = T[keyof T];

type VehicleKeys = Keys<Vehicle>; // "brand" | "model" | "year" | "color"
type VehicleValues = Values<Vehicle>; // string | number | undefined
4.2.3 何时使用接口 vs 类型别名

选择的指导原则和最佳实践

选择接口还是类型别名不是随意的,有明确的指导原则:

  • 对象形状定义:优先使用接口,特别是会被类实现的场景
  • 联合和交集类型:必须使用类型别名
  • 需要扩展的场景:接口的声明合并特性更适合
  • 复杂类型计算:类型别名的表达能力更强
  • 库的API设计:接口提供更好的扩展性
// 推荐使用接口的场景:

// 1. 定义对象的结构(特别是会被类实现的)
interface Drawable {
    draw(): void;
    getArea(): number;
}

class Circle implements Drawable {
    constructor(private radius: number) {}
    
    draw(): void {
        console.log(`绘制半径为 ${this.radius} 的圆`);
    }
    
    getArea(): number {
        return Math.PI * this.radius ** 2;
    }
}

// 2. 需要声明合并的场景
interface Window {
    customProperty: string;
}

// 在另一个文件中可以扩展
interface Window {
    anotherCustomProperty: number;
}

// 推荐使用类型别名的场景:

// 1. 联合类型
type Theme = "light" | "dark" | "auto";
type EventType = "click" | "hover" | "focus";

// 2. 交集类型
type Timestamped = {
    createdAt: Date;
    updatedAt: Date;
};

type User = {
    id: string;
    name: string;
};

type TimestampedUser = User & Timestamped;

// 3. 条件类型和映射类型
type Optional<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;

type UserWithOptionalEmail = Optional<User, 'email'>;

// 4. 计算类型
type EventMap = {
    click: MouseEvent;
    keydown: KeyboardEvent;
    load: Event;
};

type EventListener<T extends keyof EventMap> = (event: EventMap[T]) => void;

const clickHandler: EventListener<'click'> = (event) => {
    console.log(event.clientX, event.clientY); // 类型安全
};
4.2.4 接口和类型别名的互操作性
// 接口可以扩展类型别名
type Point = {
    x: number;
    y: number;
};

interface ColoredPoint extends Point {
    color: string;
}

// 类型别名可以与接口进行交集
interface Shape {
    area: number;
}

type ColoredShape = Shape & {
    color: string;
};

// 混合使用的实际示例
interface APIResponse {
    status: number;
    message: string;
}

type SuccessResponse<T> = APIResponse & {
    status: 200;
    data: T;
};

type ErrorResponse = APIResponse & {
    status: 400 | 404 | 500;
    error: string;
};

type Response<T> = SuccessResponse<T> | ErrorResponse;

// 使用示例
function handleUserResponse(response: Response<User>): void {
    if (response.status === 200) {
        // TypeScript知道这是SuccessResponse<User>
        console.log("用户数据:", response.data.name);
    } else {
        // TypeScript知道这是ErrorResponse
        console.error("错误:", response.error);
    }
}

4.3 索引签名的使用

// 字符串索引签名
interface StringDictionary {
    [key: string]: string;
}

const translations: StringDictionary = {
    hello: "你好",
    goodbye: "再见",
    thanks: "谢谢"
};

// 数字索引签名
interface NumberArray {
    [index: number]: number;
    length: number; // 显式属性必须兼容索引签名
}

const fibonacci: NumberArray = {
    0: 1,
    1: 1,
    2: 2,
    3: 3,
    4: 5,
    length: 5
};

5. 联合类型与类型缩小

处理多种可能类型的优雅方案

在实际开发中,我们经常需要处理可能是多种类型之一的值。联合类型为这种场景提供了类型安全的解决方案,而类型缩小技术让我们能够在运行时安全地处理这些值。

5.1 联合类型的强大功能

用|操作符组合多种类型可能

联合类型是TypeScript最常用的高级类型特性之一,它允许一个值是几种类型中的任意一种:

  • 类型组合:使用|操作符组合多个类型
  • 精确控制:限制值只能是指定的几种类型之一
  • 代码表达性:让类型定义更贴近业务逻辑
  • 错误预防:在编译时防止传入不正确的值
  • 智能提示:IDE能够提供精确的类型提示
// 联合类型定义
type Status = "loading" | "success" | "error";
type Id = string | number;

function handleApiResponse(status: Status, data?: unknown): void {
    switch (status) {
        case "loading":
            console.log("正在加载...");
            break;
        case "success":
            console.log("加载成功!", data);
            break;
        case "error":
            console.log("加载失败!");
            break;
    }
}

// 联合类型在函数参数中的应用
function formatId(id: Id): string {
    if (typeof id === "string") {
        return `ID: ${id.toUpperCase()}`;
    }
    return `ID: ${id.toString().padStart(6, '0')}`;
}

5.2 类型缩小技术

在运行时安全地确定具体类型

当我们有一个联合类型的值时,需要在运行时确定它的具体类型才能安全地使用。类型缩小提供了多种技术来实现这一点:

  • typeof检查:检查基本类型(string、number、boolean等)
  • instanceof检查:检查对象是否是特定类的实例
  • in操作符:检查对象是否包含特定属性
  • 自定义类型守卫:编写函数来检查复杂的类型条件
  • 控制流分析:TypeScript自动分析代码流程进行类型缩小
// 使用typeof进行类型缩小
function processValue(value: string | number | boolean): string {
    if (typeof value === "string") {
        // 在这个分支中,value确定是string类型
        return value.trim().toLowerCase();
    }
    
    if (typeof value === "number") {
        // 在这个分支中,value确定是number类型
        return value.toFixed(2);
    }
    
    // 在这个分支中,value确定是boolean类型
    return value ? "true" : "false";
}

// 使用instanceof进行类型缩小
function handleError(error: Error | string): void {
    if (error instanceof Error) {
        // error是Error类型
        console.error(error.message);
        console.error(error.stack);
    } else {
        // error是string类型
        console.error(error);
    }
}

// 使用in操作符进行类型缩小
interface Bird {
    fly(): void;
    layEggs(): void;
}

interface Fish {
    swim(): void;
    layEggs(): void;
}

function moveAnimal(animal: Bird | Fish): void {
    if ("fly" in animal) {
        // animal是Bird类型
        animal.fly();
    } else {
        // animal是Fish类型
        animal.swim();
    }
    
    // 两种类型都有layEggs方法
    animal.layEggs();
}

6. 字面量类型与模板字面量类型

精确到具体值的类型控制

字面量类型将类型系统的精确度提升到了具体的值级别,而模板字面量类型更是在编译时提供了强大的字符串操作能力。

6.1 字面量类型的精确控制

不仅仅是类型,更是具体的值

字面量类型让我们能够指定变量不仅要是某种类型,还必须是特定的值:

  • 字符串字面量:限制字符串只能是特定的几个值
  • 数字字面量:限制数字只能是特定的几个值
  • 布尔字面量:限制布尔值只能是true或false
  • 配置约束:常用于配置选项和状态管理
  • API安全性:确保API调用使用正确的参数值
// 字符串字面量类型
type Theme = "light" | "dark" | "auto";
type Size = "small" | "medium" | "large";

function setTheme(theme: Theme): void {
    document.body.className = `theme-${theme}`;
}

// 数字字面量类型
type DiceValue = 1 | 2 | 3 | 4 | 5 | 6;

function rollDice(): DiceValue {
    return Math.ceil(Math.random() * 6) as DiceValue;
}

// 布尔字面量类型
type LoadingState = true;
type ErrorState = false;

function createLoadingIndicator(isLoading: LoadingState): void {
    // isLoading只能是true
    console.log("显示加载指示器");
}

6.2 模板字面量类型的强大表达

编译时的字符串模式匹配和生成

模板字面量类型是TypeScript 4.1引入的强大特性,它将模板字符串的概念引入到类型系统中:

  • 字符串模式:使用模板语法定义字符串类型模式
  • 类型插值:在模板中插入其他类型
  • 大小写转换:内置的Capitalize、Lowercase等工具类型
  • API类型安全:为REST API路径和方法组合生成精确类型
  • 代码生成:基于模式自动生成大量相关类型
// 模板字面量类型
type EventName<T extends string> = `on${Capitalize<T>}`;
type ClickEvent = EventName<"click">; // "onClick"
type ChangeEvent = EventName<"change">; // "onChange"

// 复杂的模板字面量类型
type HttpMethod = "GET" | "POST" | "PUT" | "DELETE";
type ApiPath = "/users" | "/products" | "/orders";
type ApiEndpoint = `${HttpMethod} ${ApiPath}`;

// "GET /users" | "POST /users" | "PUT /users" | "DELETE /users" | ...

function callApi(endpoint: ApiEndpoint): void {
    const [method, path] = endpoint.split(" ");
    console.log(`调用API: ${method} ${path}`);
}

callApi("GET /users"); // ✅ 正确
// callApi("PATCH /users"); // ❌ 错误:不在联合类型中

7. 类型断言与非空断言

告诉TypeScript"我比你更了解这个类型"

有时候我们比TypeScript的类型推断更了解某个值的类型,这时就需要使用类型断言和非空断言来指导类型检查器。

7.1 类型断言的使用场景

手动指定更具体的类型

类型断言是一种告诉TypeScript编译器某个值具体类型的方式,但需要谨慎使用:

  • DOM操作:将通用的Element类型断言为具体的HTML元素类型
  • API响应:将unknown类型断言为预期的数据结构
  • 类型收窄:在复杂的类型判断中手动收窄类型
  • 第三方库:处理类型定义不完善的第三方库
  • 双重断言:极端情况下的强制类型转换(不推荐)
// 类型断言在DOM操作中的应用
const canvas = document.getElementById("myCanvas") as HTMLCanvasElement;
const context = canvas.getContext("2d");

// 角括号语法(在JSX中不可用)
const input = <HTMLInputElement>document.getElementById("myInput");

// 双重断言(谨慎使用)
const value = "hello" as unknown as number; // 强制类型转换

// 更安全的类型断言
function isString(value: unknown): value is string {
    return typeof value === "string";
}

function processUnknownValue(value: unknown): void {
    if (isString(value)) {
        // 类型守卫确保value是string类型
        console.log(value.toUpperCase());
    }
}

7.2 非空断言操作符

确信某个值不是null或undefined

非空断言操作符(!)是一种特殊的类型断言,专门用于处理可能为null或undefined的值:

  • 确定性断言:当你确定某个值不会是null或undefined时使用
  • 简化代码:避免繁琐的null检查
  • DOM查询:处理DOM查询可能返回null的情况
  • 可选属性:访问可选属性时的快捷方式
  • 谨慎使用:滥用会导致运行时错误,推荐使用可选链
// 非空断言操作符的使用
interface User {
    name: string;
    email?: string;
}

function sendEmail(user: User): void {
    // 当我们确定email一定存在时,可以使用非空断言
    const emailAddress = user.email!; // 告诉TypeScript这里不会是undefined
    console.log(`发送邮件到: ${emailAddress.toLowerCase()}`);
}

// 更安全的做法是使用可选链
function sendEmailSafely(user: User): void {
    if (user.email) {
        console.log(`发送邮件到: ${user.email.toLowerCase()}`);
    } else {
        console.log("用户没有邮箱地址");
    }
}

// 或者使用可选链操作符
function sendEmailWithOptionalChaining(user: User): void {
    console.log(`发送邮件到: ${user.email?.toLowerCase() ?? "无邮箱"}`);
}

8. null和undefined的处理

TypeScript最重要的安全特性之一

null和undefined是JavaScript中最常见的运行时错误来源,TypeScript通过严格的空检查机制帮助我们在编译时就发现这些潜在问题。

8.1 严格空检查的重要性

编译时防止空指针异常

严格空检查(strictNullChecks)是TypeScript最重要的配置选项之一:

  • 编译时检查:在编译阶段发现空值访问问题
  • 明确的可空类型:通过联合类型明确表示值可能为空
  • 强制检查:访问可能为空的值时必须先进行检查
  • 减少运行时错误:显著减少"Cannot read property of null"错误
  • 代码可靠性:提高代码的健壮性和可维护性
// 开启strictNullChecks后的代码
function findUser(id: string): User | null {
    // 模拟数据库查询
    const users = [
        { id: "1", name: "Alice" },
        { id: "2", name: "Bob" }
    ];
    
    return users.find(user => user.id === id) || null;
}

// 安全地处理可能为null的值
function displayUserName(userId: string): void {
    const user = findUser(userId);
    
    if (user !== null) {
        console.log(`用户名: ${user.name}`);
    } else {
        console.log("用户不存在");
    }
}

// 使用可选链简化代码
function displayUserNameOptional(userId: string): void {
    const user = findUser(userId);
    console.log(`用户名: ${user?.name ?? "未知用户"}`);
}

8.2 空值合并操作符

更精确的默认值处理机制

空值合并操作符(??)是ES2020引入的新特性,TypeScript完全支持并为其提供了类型安全保障:

  • 精确的空值判断:只有null和undefined才触发默认值
  • 区别于逻辑或:不会将0、""、false等视为空值
  • 配置对象:在处理配置对象时特别有用
  • 链式调用:可以与可选链操作符组合使用
  • 类型推断:TypeScript能正确推断结果类型
// 空值合并操作符的实用场景
interface AppConfig {
    apiUrl?: string;
    timeout?: number;
    retries?: number;
}

function createApiClient(config: AppConfig) {
    // 使用??提供默认值,只有null或undefined时才使用默认值
    const apiUrl = config.apiUrl ?? "https://api.example.com";
    const timeout = config.timeout ?? 5000;
    const retries = config.retries ?? 3;
    
    return {
        apiUrl,
        timeout,
        retries,
        request: (endpoint: string) => {
            console.log(`请求: ${apiUrl}${endpoint}, 超时: ${timeout}ms`);
        }
    };
}

// 注意??与||的区别
const value1 = 0 || "default"; // "default" (0被视为falsy)
const value2 = 0 ?? "default"; // 0 (只有null/undefined才使用默认值)

9. 实战练习:构建类型安全的用户管理系统

综合运用所学知识的实战项目

通过构建一个完整的用户管理系统,我们将综合运用今天学到的所有TypeScript特性。这个项目展示了如何在实际开发中应用类型安全的设计模式。

项目特点:

  • 类型安全的数据模型:使用接口和联合类型定义用户结构
  • 泛型方法:构建可复用的类型安全方法
  • 权限系统:基于角色的访问控制
  • 错误处理:完善的空值检查和错误处理
  • 实用工具类型:使用Omit、Extract等工具类型
// 定义用户相关类型
interface BaseUser {
    readonly id: string;
    name: string;
    email: string;
    createdAt: Date;
}

interface AdminUser extends BaseUser {
    role: "admin";
    permissions: Permission[];
}

interface RegularUser extends BaseUser {
    role: "user";
    lastLoginAt?: Date;
}

type User = AdminUser | RegularUser;
type Permission = "read" | "write" | "delete" | "admin";

// 用户管理类
class UserManager {
    private users: Map<string, User> = new Map();

    createUser(userData: Omit<User, 'id' | 'createdAt'>): User {
        const id = this.generateId();
        const user: User = {
            ...userData,
            id,
            createdAt: new Date()
        };
        
        this.users.set(id, user);
        return user;
    }

    getUser(id: string): User | undefined {
        return this.users.get(id);
    }

    getUsersByRole<T extends User['role']>(role: T): Extract<User, { role: T }>[] {
        const result: Extract<User, { role: T }>[] = [];
        
        for (const user of this.users.values()) {
            if (user.role === role) {
                result.push(user as Extract<User, { role: T }>);
            }
        }
        
        return result;
    }

    hasPermission(userId: string, permission: Permission): boolean {
        const user = this.getUser(userId);
        
        if (!user) return false;
        
        if (user.role === "admin") {
            return user.permissions.includes(permission);
        }
        
        return false; // 普通用户没有特殊权限
    }

    private generateId(): string {
        return Math.random().toString(36).substr(2, 9);
    }
}

// 使用示例
const userManager = new UserManager();

// 创建管理员用户
const admin = userManager.createUser({
    name: "管理员",
    email: "admin@example.com",
    role: "admin",
    permissions: ["read", "write", "delete", "admin"]
});

// 创建普通用户
const regularUser = userManager.createUser({
    name: "普通用户",
    email: "user@example.com",
    role: "user"
});

// 类型安全的权限检查
console.log(userManager.hasPermission(admin.id, "admin")); // true
console.log(userManager.hasPermission(regularUser.id, "admin")); // false

// 获取特定角色的用户
const admins = userManager.getUsersByRole("admin");
const users = userManager.getUsersByRole("user");

🎯 总结与下一步

第一天的学习成果总结

经过今天的深度学习,我们已经掌握了TypeScript的核心基础知识。这些知识点构成了TypeScript开发的坚实基础,为后续的高级特性学习打下了良好的根基。

今天的核心收获:

今天我们深入学习了TypeScript的核心概念:

  1. 静态类型检查 - 在编译时发现错误,提高代码质量
  2. 基础类型系统 - string、number、boolean等原始类型的正确使用
  3. 函数类型 - 参数和返回值的类型注解,上下文类型推断
  4. 对象类型 - 接口、类型别名、索引签名的使用
  5. 联合类型 - 类型组合和类型缩小技术
  6. 字面量类型 - 精确的值控制和模板字面量类型
  7. 类型断言 - 类型转换和非空断言的安全使用
  8. 空值处理 - null、undefined的严格检查和安全处理

下一步学习计划

循序渐进的TypeScript进阶之路

掌握了今天的基础知识后,我们将继续深入TypeScript的高级特性:

  • 第二天:泛型、条件类型、映射类型 - 构建可复用的类型系统
  • 第三天:类与继承、装饰器、模块系统 - 面向对象编程实践
  • 第四天:高级类型操作、工具类型、类型编程 - 类型级别的编程
  • 第五天:实战项目:构建完整的TypeScript应用 - 综合项目实战

学习建议:

  • 多写代码,在实践中巩固理论知识
  • 启用strict模式,体验完整的TypeScript类型安全
  • 阅读优秀开源项目的TypeScript代码
  • 逐步将现有JavaScript项目迁移到TypeScript

深度解析 TypeScript 泛型

作者 袋鱼不重
2025年6月25日 17:38

在 TypeScript 的类型系统中,泛型(Generics)是构建可复用、类型安全组件的核心工具。本文将结合《Effective TypeScript》中关于泛型的章节,系统讲解泛型的设计原则、实际应用场景及最佳实践,帮助开发者掌握这一关键特性。

一、泛型的核心价值:类型安全与代码复用

1. 泛型的本质:类型参数化

泛型允许在定义函数、类或接口时,不预先指定具体类型,而是通过类型参数(如 <T>)在调用时动态指定。例如:

// 基础泛型函数:保持输入输出类型一致

function identity<T>(arg: T): T {

  return arg;

}

 

const str = identity<string>("hello"); // 显式指定类型

const num = identity(42); // 类型推断为 number

《Effective TypeScript》建议

  • 优先使用泛型而非 any,以保留类型信息(Item 6)。
  • 泛型类型参数通常用 TUK 等大写字母命名,以明确其类型占位符身份(Item 23)。

二、泛型的实际应用场景

1. 泛型函数:处理多种类型输入

场景1:统一输入输出类型

// 基础泛型函数:保持输入输出类型一致

function logAndReturn<T>(arg: T): T {

  console.log(arg);

  return arg;

}

《Effective TypeScript》建议

  • 避免过度泛型化:仅在需要复用类型逻辑时使用泛型(Item 7)。

场景2:多类型参数与约束

// 多类型参数泛型函数

function toTuple<T, U>(first: T, second: U): [T, U] {

  return [first, second];

}

 

// 泛型约束:确保类型包含特定属性

interface Lengthwise {

  length: number;

}

function loggingIdentity<T extends Lengthwise>(arg: T): T {

  console.log(arg.length);

  return arg;

}

《Effective TypeScript》建议

  • 使用 extends 约束泛型参数范围,增强类型安全性(Item 10)。
  • 优先通过接口定义约束条件,而非直接在函数中硬编码(Item 11)。

2. 泛型接口:定义灵活的类型契约

场景1:函数类型接口

// 泛型函数接口

interface Transformer<T, U> {

  (input: T): U;

}

 

const stringToLength: Transformer<string, number> = (str) => str.length;

场景2:对象类型接口

// 泛型对象接口

interface KeyValuePair<K, V> {

  key: K;

  value: V;

}

 

const pair: KeyValuePair<string, number> = { key: "age", value: 30 };

《Effective TypeScript》建议

  • 泛型接口适用于需要复用的类型契约(如容器类、工具函数)。
  • 避免过度嵌套泛型接口,保持代码可读性(Item 24)。

3. 泛型类:构建可复用的数据结构

场景1:通用容器类

// 泛型类:存储任意类型的数据

class Container<T> {

  private value: T;

  constructor(initialValue: T) {

    this.value = initialValue;

  }

  getValue(): T {

    return this.value;

  }

  setValue(newValue: T): void {

    this.value = newValue;

  }

}

 

const stringContainer = new Container<string>("initial");

const numberContainer = new Container<number>(42);

《Effective TypeScript》建议

  • 泛型类适用于需要类型安全的容器(如栈、队列、链表)。
  • 避免在泛型类中过度使用类型推断,显式指定类型以提升可读性(Item 25)。

三、高级泛型技巧:结合《Effective TypeScript》的进阶实践

1. 条件类型:动态决定类型输出

// 条件类型:根据输入类型动态返回类型

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

type A = IsString<string>; // "yes"

type B = IsString<number>; // "no"

《Effective TypeScript》建议

  • 条件类型适用于需要根据输入类型动态返回类型的场景(如 API 响应解析)。
  • 避免过度复杂的条件类型嵌套,保持类型定义简洁(Item 26)。

2. 映射类型:基于已有类型生成新类型

// 映射类型:将对象属性转换为可选或只读

type Readonly<T> = {

  readonly [P in keyof T]: T[P];

};

type Partial<T> = {

  [P in keyof T]?: T[P];

};

 

interface User {

  name: string;

  age: number;

}

type ReadonlyUser = Readonly<User>;

type PartialUser = Partial<User>;

《Effective TypeScript》建议

  • 映射类型适用于需要批量修改对象属性类型的场景(如 API 响应标准化)。
  • 优先使用内置的映射类型(如 PartialReadonly),而非重复定义(Item 27)。

3. 类型推断(infer):提取类型信息

// 类型推断:提取数组元素的类型

type ElementType<T> = T extends (infer U)[] ? U : never;

type Arr = [string, number];

type ArrElement = ElementType<Arr>; // string | number

《Effective TypeScript》建议

  • infer 适用于需要从复杂类型中提取信息的场景(如解析函数参数类型)。
  • 避免在类型推断中过度使用嵌套条件,保持类型定义清晰(Item 28)。

四、泛型的最佳实践:遵循《Effective TypeScript》的建议

  1. 明确类型约束:通过接口或类型别名定义泛型约束,而非直接在函数中硬编码。
  2. 避免过度泛型化:仅在需要复用类型逻辑时使用泛型,避免不必要的类型参数。
  3. 显式指定类型:在复杂场景中显式指定泛型类型,而非依赖类型推断。
  4. 利用内置工具类型:优先使用 TypeScript 内置的泛型工具类型(如 PartialReadonly),而非重复定义。

总结

泛型是 TypeScript 中实现类型安全与代码复用的核心工具。通过结合《Effective TypeScript》的建议,开发者可以更好地设计泛型函数、接口和类,避免常见陷阱,提升代码质量。

下一步建议

  • 尝试将泛型应用于实际项目(如 Vue 组件、Redux 状态管理)。
  • 阅读 TypeScript 官方文档中的泛型章节,深入理解高级特性(如泛型约束、条件类型)。
  • 参与开源项目,观察其他开发者如何使用泛型解决实际问题。

希望本文能帮助你掌握 TypeScript 泛型的核心用法,并灵活应用于实际开发中!

❌
❌