我在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 })
有人可以看到我在做什么吗?
谢谢!
答案 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
在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 }
交集类型将多种类型组合为一种。这样,您便可以将现有类型加在一起,以获得具有所需所有功能的单个类型。
该死的,我希望我之前对这两行更加关注。
我被 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
键都是字符串。嗯...不完全是。
引用此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;
keys
和dogKeys
都解析为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
表示我是说字符串是字符串或数字,据我所知,它始终是真实的。