字符串不能用于索引类型'T'

时间:2020-09-15 00:20:29

标签: typescript

我在TypeScript中具有严格的模式,即使在类型上设置了字符串索引器,在设置对象上的索引时也会遇到问题。

禁用noImplicitAny可以解决此问题,但我宁愿不这样做。

我对this的答案进行了仔细研究,但建议并未获得成功。

type stringKeyed = { [key: string]: any }
type dog = stringKeyed & { name: string }
type cat = stringKeyed & { lives: number }

function setLayerProps<T extends dog | cat>(
    item: T,
    props: Partial<T>
) {
    if (item) {
        Object.entries(props).forEach(([k, v]) => {
                item[k] = v; // Error: Type 'string' cannot be used to index type 'T'.ts(2536)

        });
    }
}


let d = { name: 'fido' } as dog;
let c = { lives: 9 } as cat;
setLayerProps(d, { name: 'jim' })
setLayerProps(c, { lives: --c.lives })

有人可以看到我在做什么吗?

谢谢!

2 个答案:

答案 0 :(得分:3)

这是#30769的影响和一个已知的重大变化。我们以前允许这样的疯狂东西,没有错误:

function foo<T extends Record<string, any>>(obj: T) {
    obj["s"] = 123;
    obj["n"] = "hello";
}
let z = { n: 1, s: "abc" };
foo(z);
foo([1, 2, 3]);
foo(new Error("wat"));

通常,约束Record 实际上并不确保参数具有字符串索引签名,而只是确保参数的属性可分配给XXX类型。因此,在上面的示例中,您可以有效地传递任何对象,并且该函数无需进行任何检查即可写入任何属性。

在3.5中,我们强制您只能在知道有问题的对象具有索引签名的情况下才能写入索引签名元素。因此,您需要对clone函数进行一些小的更改:

function clone<T extends Record<string, any>>(obj: T): T {
    const objectClone = {} as Record<string, any>;

    for (const prop of Reflect.ownKeys(obj).filter(isString)) {
        objectClone[prop] = obj[prop];
    }

    return objectClone as T;
}

通过此更改,我们现在知道objectClone具有字符串索引签名。

所以您的代码应该是

function setLayerProps<T extends dog | cat>(
    item: T,
    props: Partial<T>
) {
    if (item) {
        Object.entries(props).forEach(([k, v]) => {
            (item as dog | cat)[k] = v;

        });
    }
}

参考:https://github.com/microsoft/TypeScript/issues/31661#issuecomment-497138929

答案 1 :(得分:2)

免责声明:这并不是真正的答案,而是我在这个主题上徘徊的结果。为了清楚起见,我正在写一个答案。

TL; DR:对象键类型为string | number,而不是string

使其正常工作

在TypeScript 4.0.2下,我使用了TypeScript playground,最后得到了something which works

type stringKeyed = { [key: string]: any };
type dog = stringKeyed & { name: string };
type cat = stringKeyed & { lives: number };

function setLayerProps<T extends dog | cat>(item: T, props: Partial<T>) {
  if (item) {
    Object.entries(props).forEach(([k, v]) => {
      // With an explicit key casting, TypeScript does not complain
      const key = k as keyof T;
      item[key] = v;
    });
  }
}

let d = { name: "fido" } as dog;
let c = { lives: 9 } as cat;
setLayerProps(d, { name: "jim" });
setLayerProps(c, { lives: --c.lives });

console.log(d); // [LOG]: { "name": "jim" }
console.log(c); // [LOG]: { "lives": 8 }

为什么起作用?

交叉路口类型...不是交叉路口

根据TypeScript documentation

交集类型将多种类型组合为一种。这样,您便可以将现有类型加在一起,以获得具有所需所有功能的单个类型。

该死的,我希望我之前对这两行更加关注。

我被 intersection 的措辞所迷惑,并认为dog类型只能使用一个键(name),但事实并非如此。根据{{​​3}},交集类型使子类型继承了相交类型的所有属性。

换句话说:

type stringKeyed = { [key: string]: any };
type dog = stringKeyed & { name: string };

表示

  • 对于name键,需要一个字符串值
  • 对于其他任何键,都可以使用任何值

那么下面的声明是正确的:

let d: dog = {name: 'fido' };
d.something = else;
d.age = 3;

在此阶段,我们试图得出的唯一结论是所有dog键都是字符串。嗯...不完全是。

JavaScript ...不知道数字键和字符串键T_T之间的区别

引用此Intersection types in TypeScript article in codingblast

如TypeScript 2.9发行说明中所定义,如果您对具有字符串索引签名的接口进行键控,则它将返回字符串和数字的并集

excellent StackOverflow answer提到:

如果该类型具有字符串或数字索引签名,则keyof将改为返回这些类型:

type Mapish = { [k: string]: boolean };
type M = keyof Mapish;
//   ^ = type M = string | number

为了说服自己,我尝试了以下操作:

type keys = keyof stringKeyed;
type dogKeys = keyof dog;
const keyAsStr: dogKeys = "this is OK";
const keyAsNum: dogKeys = 42;

keysdogKeys都解析为string | number

字符串!==字符串|数字

现在,为什么出现错误 Error:类型'string'不能用于索引类型'T'.ts(2536)的原因仅仅是因为密钥类型永远不能为{{1 }} 只要。 “最小”版本为string,因此键入错误。有趣的是,当尝试获取string | number中的T键类型时,将鼠标悬停在该类型上不会立即显示该类型。所以我尝试了

setLayerProps

我最终遇到以下错误:

// Inside the setLayerProps function
type tKey = keyof T;
const asStr: tKey = "43";
const asNum: tKey = 43;
const asObj: tKey = { a: "a" };

所以 TypeScript确实期望Type '{ a: string; }' is not assignable to type 'keyof T'. Type '{ a: string; }' is not assignable to type 'string | number'. Type '{ a: string; }' is not assignable to type 'number'.

由于某种原因,我还不了解,这不起作用:

string | number

由于它会导致错误:

const key = k as string | number;

为什么显式强制转换是安全的

Type 'number' cannot be used to index type 'T'.(2536) Type 'string' cannot be used to index type 'T'.(2536) 函数中,我们知道setLayerProps键是item还是string。如此明确地投射

number

表示我是说字符串是字符串或数字,据我所知,它始终是真实的。