为嵌套对象建立索引时:TypeScript(7053):元素隐式具有“any”类型,因为类型X的表达式不能用于索引类型Y

62lalag4  于 2023-05-19  发布在  TypeScript
关注(0)|答案(1)|浏览(311)

我在尝试索引嵌套对象时遇到了TypeScript问题(7053): Element implicitly has an 'any' type because expression of type X can't be used to index type Y。TypeScript似乎无法正确建立父键和子键之间的连接,如TheTypeKeysCombined甚至AllowedKeySubkeyPairs中定义的那样。
.split()方法的返回值有一些问题,因为手动输入父键和子键可以工作。但我不知道我错过了什么。
构建体TheTypeKeysCombined例如'second.secondThird'需要保持这种格式,然后使用.split()方法只是一个想法,当TheTypeKeysCombined传递时提取父键和子键。
TypeScript playground示例。

const nestedConstObj = {
    first: {
        firstFirst: 'firstFirstVal',
    },
    second: {
        secondFirst: 'secondFirstVal',
        secondSecond: 'secondSecondVal',
        secondThird: 'secondThirdVal',
        secondFourth: 'secondFourthVal',
    },
    third: {
        thirdFirst: 'thirdFirstVal',
        thirdSecond: 'thirdFirstVal',
    },
} as const;

type TheType = typeof nestedConstObj;
type TheTypeKeys = {[K in keyof TheType]: keyof TheType[K]};
type TheTypeKeysCombined = {[K in keyof TheTypeKeys]: `${K}.${TheTypeKeys[K]}`}[keyof TheTypeKeys];
// type TheTypeKeysCombined = "first.firstFirst" | "second.secondFirst" | "second.secondSecond" | "second.secondThird" | "second.secondFourth" | "third.thirdFirst" | "third.thirdSecond"

// Let's say we get something like this:
const myQuery : TheTypeKeysCombined = 'second.secondThird';

// Try #1 --- THE ISSUE ---
const [keyLvl1_1, keyLvl1_2] = myQuery.split('.');
const myVal1 = nestedConstObj[keyLvl1_1][keyLvl1_2];
// -> Element implicitly has an 'any' type because expression of type 'string' can't be used to index type ...

// Try manually
const [keyLvlM_1, keyLvlM_2] : AllowedKeySubkeyPairs = ['second', 'secondThird'];
const myValM = nestedConstObj[keyLvlM_1][keyLvlM_2];
// -> Works, but I need rather with `myQuery.split('.')`

// Try #2
const [keyLvl2_1, keyLvl2_2] = myQuery.split('.');
const myVal2 = nestedConstObj[keyLvl2_1 as keyof TheType][keyLvl2_2 as keyof TheType[keyof TheType]];
// -> Works but `myVal2` is of type 'never'

// Try #3
const [keyLvl3_1, keyLvl3_2] = myQuery.split('.') as [keyof TheType, keyof TheType[keyof TheType]];
const myVal3 = nestedConstObj[keyLvl3_1][keyLvl3_2];
// -> Works but as well, the `myVal3` is of type 'never', becaue `keyLvl3_2` is of type 'never'

// Try #4
type AllowedKeySubkeyPairs = {[K in keyof TheType]: {[K2 in keyof TheType[K]] : [K, K2]}[keyof TheType[K]]}[keyof TheType];
// type AllowedKeySubkey = ["first", "firstFirst"] | ["second", "secondFirst"] | ["second", "secondSecond"] | ["second", "secondThird"] | ["second", "secondFourth"] | ["third", "thirdFirst"] | [...]
const [keyLvl4_1, keyLvl4_2] = myQuery.split('.') as AllowedKeySubkeyPairs;
const myVal4 = nestedConstObj[keyLvl4_1][keyLvl4_2];
// -> Element implicitly has an 'any' type because expression of type ... can't be used to index type ...
8ulbf1ek

8ulbf1ek1#

String.prototype.split()无论什么都返回string[],这就是为什么仅仅使用它,我们将无法得到正确的推断。
作为一种选择,我们可以编写一个泛型类型Split,它将接受字符串和分隔符,并返回一个文本元组。基本上,它是split的类型版本:

type Split<Str extends string, Del extends string> = string extends Str
  ? string[]
  : '' extends Str
  ? []
  : Str extends `${infer T}${Del}${infer U}`
  ? [T, ...Split<U, Del>]
  : [Str];

说明:
我们需要检查Str是否是任何字符串。在这种情况下,没有必要继续下去,因为它不是一个文字字符串:string extends Str
接下来,我们检查Str是否为空,这也阻止了我们继续:'' extends Str
否则,我们使用推断将Str拆分为T,即Del之前的部分和U,即Del之后的部分。如果这是真的,那么我们将T放入一个元组中,并递归地调用Split以获取Str的其余部分,即U。如果我们不能推断出TU,这意味着我们不能再除法了,因此我们只返回[Str]
我们需要创建一个使用上面类型的函数:

const splitTyped = <Str extends string, Del extends string>(
  str: Str,
  del: Del,
): Split<Str, Del> => {
  return str.split(del) as Split<Str, Del>;
};

不幸的是,我们需要使用Assert,因为split返回string[]
Playground

相关问题