Typescript中的对象类型

发布于:2025-05-11 ⋅ 阅读:(10) ⋅ 点赞:(0)

对象类型


1. 对象类型定义

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

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

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

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

2. 属性修饰符

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

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

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

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

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

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

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

3. 索引签名

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

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

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

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

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

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

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

4. 超额属性检查

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

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

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

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

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

5. 类型扩展与交叉

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

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

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

6. 工具类型

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

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

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

    type ReadOnlyPerson = Readonly<Person>;
    

7. 实际应用场景

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

    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}`);
        }
      });
    }
    
缓存实现
  • 使用索引签名管理缓存数据:

    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. 高级技巧

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

    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();
    }
    
映射类型
  • 自定义映射类型转换属性:

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

10. 常见误区

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

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

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

    • readonly 仅限制属性本身不可重写,不影响内部对象的修改。

网站公告

今日签到

点亮在社区的每一天
去签到