我有两种类型声明,一种接受字符串,另一种只将数字作为键。
testFunction不编译,说明MyStringKeyObject | MyNumberKeyObject类型的属性不存在。
有人能解释一下吗?
type MyStringKeyObject = {
[key: string]: boolean;
};
type MyNumberKeyObject = {
[key: number]: boolean;
};
const testFunction = <T extends MyStringKeyObject | MyNumberKeyObject>(
generic: T
): void => {
generic[1];
generic.doesNotCompile;
};我正在使用的tsconfig:
{
"compilerOptions": {
"target": "es5",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noFallthroughCasesInSwitch": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"noEmit": true,
"jsx": "react-jsx"
},
"include": [
"src"
]
}发布于 2022-07-11 15:31:53
这种情况有点不寻常,因为您有一对索引类型,但接下来您将使用硬编码值对它们进行索引。通常,您会从对象中派生出密钥,这样就不会遇到这个问题。在您的情况下,这是因为代码中有显式的1和doesNotExist。
问题是,在编译时,generic可能是MyStringKeyObject 或 MyNumberKeyObject.因此,TypeScript不知道要应用哪组规则,字符串键对象的规则或数字键对象的规则。
generic.doesNotExist的错误是非常有意义的:
属性'doesNotCompile‘不存在于'MyStringKeyObject \\ MyNumberKeyObject’类型上。另一种属性'doesNotCompile‘在'MyNumberKeyObject’类型中不存在。(2339)
该表达式对于MyStringKeyObject有效,但对MyNumberKeyObject无效,但generic可能是其中之一。
generic[1]的错误略有不同:
元素隐式具有'any‘类型,因为类型'1’的表达式不能用于索引类型'MyStringKeyObject \ MyNumberKeyObject‘。另一种属性'1‘不存在于'MyStringKeyObject \ MyNumberKeyObject’类型上。(7053)
我对generic[1]上的错误感到有点惊讶(但是,我仍然非常擅长TypeScript),因为如果您只是使用MyStringKeyObject或MyNumberKeyObject (而不是两者的结合),那么这两者都是有效的。(它允许使用字符串键对象,因为在JavaScript中,可以使用数字对任何对象进行“索引”,然后将数字转换为字符串。)尽管如此,您基本上还是遇到了与另一个问题相同的问题: TypeScript需要知道要应用哪些规则。
解决办法是找到一种方法来区分工会的一名成员和另一名成员,并相应地进行分支。但在运行时,您不能问对象是字符串索引对象还是数字索引对象。你有几个选择:
MyStringKeyObject和MyNumberKeyObject使用单独的函数。因为您无论如何都要分别处理它们(因为硬编码的键),这似乎是合乎逻辑的。下面是#2的一个例子:
type MyStringKeyObject = {
__type__: "string";
} & {
[key: string]: boolean;
};
type MyNumberKeyObject = {
__type__: "number";
[key: number]: boolean;
};
const testFunction = <T extends MyStringKeyObject | MyNumberKeyObject>(generic: T): void => {
if (generic.__type__ === "number") {
generic[1];
} else {
generic.doesNotCompile; // But it does now. :-)
}
};在if/else的每个分支中,TypeScript都知道它所处理的类型( if将generic的类型缩小为联合的一个或另一个),因此您可以使用这些硬编码的属性名称。
发布于 2022-07-11 16:02:14
您的规则不允许您假设参数扩展了一种类型或另一种类型,因此您只能使用重叠的属性,在本例中没有重叠。(更正:从技术上讲,数字键是重叠的,因为它们被隐式转换为字符串)。
看起来您可能需要一个intersection而不是union。
type MyStringKeyObject = {
[key: string]: boolean;
};
type MyNumberKeyObject = {
[key: number]: boolean;
};
const testFunction = <T extends MyStringKeyObject & MyNumberKeyObject>(
generic: T
): void => {
generic[1];
generic['doesNotCompile'];
};这是因为它说generic包含字符串键和数字键。而您的则表示,它可能包含数字键或字符串键。
对于一个联合,您需要在假设某个类型的键之前对参数进行类型检查。
您可以使用类与instanceof一起对参数进行类型检查。
class MyStringKeyObject {
[key: string]: boolean;
}
class MyNumberKeyObject {
[key: number]: boolean;
}
const testFunction = <T extends MyStringKeyObject | MyNumberKeyObject>(
generic: T
): void => {
if (generic instanceof MyNumberKeyObject) {
console.log('number key', generic[1]);
} else if (generic instanceof MyStringKeyObject) {
console.log('string key', generic['string']);
} else {
console.log('Invalid parameter', generic);
}
};
class NumberKeyExt extends MyNumberKeyObject {}
class StringKeyExt extends MyStringKeyObject {}
const numberKey = new MyNumberKeyObject();
const numberKeyExt = new NumberKeyExt();
const stringKey = new StringKeyExt();
const stringKeyExt = new MyStringKeyObject();
numberKey[1] = true;
numberKeyExt[1] = true;
stringKey['string'] = true;
stringKeyExt['string'] = true;
testFunction(numberKey); // number key true
testFunction(numberKeyExt); // number key true
testFunction(stringKey); // string key true
testFunction(stringKeyExt); // string key trueinstanceof依赖于这样一个事实:对象是使用构造函数创建的。
https://stackoverflow.com/questions/72940589
复制相似问题