Typescript中的对象类型

对象类型


1. 对象类型定义

基本语法
  • 匿名对象类型:直接在函数参数中声明。

    ts 复制代码
    function greet(person: { name: string; age: number }) {
      return "Hello " + person.name;
    }
  • 接口(Interface):命名复用性强的对象类型。

    ts 复制代码
    interface Person {
      name: string;
      age: number;
    }
    
    function greet(person: Person) {
      return "Hello " + person.name;
    }
  • 类型别名(Type Alias):适用于更复杂的类型组合。

    ts 复制代码
    type Person = {
      name: string;
      age: number;
    };
    
    function greet(person: Person) {
      return "Hello " + person.name;
    }
差异比较
  • 接口 vs 类型别名
    • 接口:支持合并(相同名称的接口会自动合并),适合定义契约。
    • 类型别名 :一次性定义,不能重复声明,适合联合类型(type ID = string | number)。

2. 属性修饰符

可选属性
  • 使用 ? 标记属性为可选,调用时可省略。

    ts 复制代码
    interface PaintOptions {
      shape: Shape;
      xPos?: number;
      yPos?: number;
    }
    
    paintShape({ shape }); // 合法
    paintShape({ shape, xPos: 100 }); // 合法
  • 默认值处理

    • 使用解构赋值设置默认值,简化代码。

      ts 复制代码
      function paintShape({ shape, xPos = 0, yPos = 0 }: PaintOptions) {
        console.log("x coordinate at", xPos);
      }
只读属性
  • 使用 readonly 防止属性被修改。

    ts 复制代码
    interface SomeType {
      readonly prop: string;
    }
    
    const obj: SomeType = { prop: "value" };
    obj.prop = "new value"; // ❌ 错误:不可写
  • 嵌套对象的只读性

    • readonly 只限制顶层属性,不影响内部对象的可变性。

      ts 复制代码
      interface Home {
        readonly resident: { name: string; age: number };
      }
      
      let home: Home = { resident: { name: "Alice", age: 30 } };
      home.resident.age++; // ✅ 允许修改内部属性
      home.resident = { ... }; // ❌ 不允许重新赋值

3. 索引签名

动态键值对
  • 描述未知属性名的对象。

    ts 复制代码
    interface StringArray {
      [index: number]: string;
    }
    
    const arr: StringArray = ["a", "b"]; // ✅
    console.log(arr[1]); // 输出 "b"
  • 字符串索引签名

    • 限制所有属性值类型一致,适用于字典模式。

      ts 复制代码
      interface Dictionary {
        [key: string]: number;
      }
      
      const dict: Dictionary = { a: 1, b: 2 }; // ✅
  • 混合固定属性与索引签名

    • 固定属性类型必须是索引签名类型的子集。

      ts 复制代码
      interface NumberOrStringDictionary {
        [key: string]: number | string;
        length: number; // ✅ 允许
        name: string; // ✅ 允许
      }
只读索引签名
  • 防止通过索引修改值。

    ts 复制代码
    interface ReadonlyStringArray {
      readonly [index: number]: string;
    }
    
    let arr: ReadonlyStringArray = ["a", "b"];
    arr[0] = "c"; // ❌ 错误:不可修改

4. 超额属性检查

严格检查机制
  • 创建对象字面量时,若包含目标类型未声明的属性,TypeScript 报错。

    ts 复制代码
    interface SquareConfig {
      color?: string;
      width?: number;
    }
    
    createSquare({ colour: "red", width: 100 }); // ❌ 'colour' 不存在于 SquareConfig 中
绕过检查的方法
  1. 类型断言(需谨慎使用):

    ts 复制代码
    createSquare({ colour: "red", width: 100 } as SquareConfig);
  2. 添加字符串索引签名

    ts 复制代码
    interface SquareConfig {
      [propName: string]: unknown; // 允许任意属性
      color?: string;
      width?: number;
    }
  3. 中间变量赋值

    ts 复制代码
    const options = { colour: "red", width: 100 };
    createSquare(options); // ✅ 无错误

5. 类型扩展与交叉

接口扩展
  • 使用 extends 继承并扩展其他接口。

    ts 复制代码
    interface BasicAddress {
      street: string;
      city: string;
    }
    
    interface FullAddress extends BasicAddress {
      postalCode: string;
    }
交叉类型(Intersection Types)
  • 使用 & 合并多个类型。

    ts 复制代码
    interface Colorful {
      color: string;
    }
    
    interface Circle {
      radius: number;
    }
    
    type ColorfulCircle = Colorful & Circle;
    
    const cc: ColorfulCircle = {
      color: "red",
      radius: 42,
    };

6. 工具类型

常用工具类型
  • Partial:将所有属性变为可选。

    ts 复制代码
    type PartialPerson = Partial<Person>; // name?: string; age?: number
  • Required:将所有属性变为必填。

    ts 复制代码
    type RequiredPerson = Required<Person>; // name: string; age: number
  • Readonly:将所有属性设为只读。

    ts 复制代码
    type ReadOnlyPerson = Readonly<Person>;

7. 实际应用场景

表单数据处理
  • 动态表单字段类型化:

    ts 复制代码
    interface FormData {
      [fieldName: string]: string | number | boolean | undefined;
    }
    
    function processForm(data: FormData) {
      Object.entries(data).forEach(([key, value]) => {
        if (typeof value === "string") {
          console.log(`${key}: ${value}`);
        }
      });
    }
缓存实现
  • 使用索引签名管理缓存数据:

    ts 复制代码
    interface Cache<T> {
      [key: string]: { value: T; timestamp: number };
    }
    
    class DataCache<T> {
      private cache: Cache<T> = {};
      
      set(key: string, value: T): void {
        this.cache[key] = { value, timestamp: Date.now() };
      }
      
      get(key: string): T | undefined {
        return this.cache[key]?.value;
      }
    }

8. 最佳实践

  1. 优先使用接口:定义明确的契约,便于扩展和维护。
  2. 合理使用可选属性:避免强制要求不必要的字段。
  3. 避免过度使用索引签名 :可能导致类型不安全,优先考虑 MapRecord
  4. 谨慎使用类型断言:确保类型正确性,避免隐藏错误。
  5. 利用工具类型:减少重复代码,提升类型安全性。

9. 高级技巧

泛型对象类型
  • 结合泛型实现灵活的类型约束:

    ts 复制代码
    interface ApiResponse<T> {
      data: T;
      meta: { [key: string]: unknown };
    }
    
    async function fetchData<T>(url: string): Promise<ApiResponse<T>> {
      const response = await fetch(url);
      return response.json();
    }
映射类型
  • 自定义映射类型转换属性:

    ts 复制代码
    type Optional<T> = {
      [K in keyof T]?: T[K];
    };
    
    type OptionalPerson = Optional<Person>; // name?: string; age?: number

10. 常见误区

  • 索引签名与固定属性冲突

    • 固定属性类型必须符合索引签名类型,否则报错。

      ts 复制代码
      interface User {
        [key: string]: string | number; // ❌ 下方的布尔值不符合
        isAdmin: boolean;
      }
  • 只读属性与不可变性的区别

    • readonly 仅限制属性本身不可重写,不影响内部对象的修改。
相关推荐
无双_Joney几秒前
[更新迭代 - 1] Nestjs 在24年底更新了啥?(功能篇)
前端·后端·nestjs
在云端易逍遥2 分钟前
前端必学的 CSS Grid 布局体系
前端·css
EMT2 分钟前
在 Vue 项目中使用 URL Query 保存和恢复搜索条件
javascript·vue.js
ccnocare3 分钟前
选择文件夹路径
前端
艾小码4 分钟前
还在被超长列表卡到崩溃?3招搞定虚拟滚动,性能直接起飞!
前端·javascript·react.js
闰五月5 分钟前
JavaScript作用域与作用域链详解
前端·面试
泉城老铁8 分钟前
idea 优化卡顿
前端·后端·敏捷开发
前端康师傅8 分钟前
JavaScript 作用域常见问题及解决方案
前端·javascript
司宸10 分钟前
Prompt结构化输出:从入门到精通的系统指南
前端