如何理解typescript的类型是否另一个类型的子集?

interface AT {
  // [k: string]: string // 加上这个就没问题了
  a: string
  b: string
}
const param: AT = {
  a: 'aa',
  b: 'bb'
}
f(param) // error ts2345
function f(a: {[k: string]: string}) {}
// function f(a: {[k: string]: any}) {} // 改这里也可以

请问为什么ts这么奇怪,该怎么理解?

按照我的理解,AT{[k: string]: string}的子集,应该不用额外声明的,就好像999可以传给number一样。

反而ts好像认为AT{[k: string]: any}的子集,为什么呢?

阅读 121
评论
    1 个回答
    will
    • 2.5k

    好问题。

    {a: string} 是不是 {[k:string]: string} 的子集?我们先看下面的代码:

    interface T{
      a: string;
    }
    
    interface K{
      [k:string]: string;
    }
    
    interface U extends T {
      b: number;
    }
    
    // 这是合法的操作吧,这里定义的字面量符合 type U,自然可以
    // 赋值给 type T 的类型
    let foo:U = {a:'hello', b: 1};
    let bar:T = foo; 
    
    // 而如果 T 类型是 K 类型的子集,那么意味着这个操作也是
    // 合法的,但是这明显不符合 K 的类型定义了啊?
    let bzz:K = bar; 
    
    // TS 如何处理这尴尬局面? type Q 是啥?string 还是 number?
    type Q = typeof bzz['b'];
    

    如上,如果规定{a: string}{[k:string]: string} 的子集,那么会出现子类型与父类型冲突的情况。事实上,这种类型定义叫做索引签名(index signature),而官方文档也是有明文规定的,定义了索引签名的接口允许定义其他具名属性,但是类型必须和索引签名相同。即:

    interface K{
      [key: string]: string;
      name: string; // ok
    }
    
    interface P extends K {
      b: number; // wrong
      c: string; // ok
    }

    显然,{a: string,b:string} 不是 {[k:string]: string} 的子集,因为前者可以有无限的扩展可能,而后者存在扩展限制。而 {[key:string]:any}就没有问题,本质上它和object类型是一回事。即题目中的AT确实是{[key:string]:any}的子集。

    评论 赞赏
      撰写回答

      登录后参与交流、获取后续更新提醒