我尝试使用一个“自由”创建的string
作为对象的键。
interface CatState {
name: string,
selected: boolean,
color: string,
height: number
}
interface DogState{
name: string,
selected: boolean,
race: string,
age: number,
eyeColor: string
}
export interface Animals {
animals: {
cat: {
cats: CatState[],
allSelected: boolean,
},
dog: {
dogs: DogState[],
allSelected: boolean,
},
}
};
const selectAnimal = (allAnimals: Animals, animal: keyof Animals['animals'], index:number) => {
const animalPlural = `${animal}s` as keyof Animals['animals'][typeof animal]
allAnimals.animals[animal][animalPlural][index].selected= true
}
这将突出显示.selected
并显示以下消息
类型“boolean”上不存在属性“selected”。
这里有一个Playground。是否有解决方法,或者根本不可能?
2条答案
按热度按时间uplii1fm1#
为了使其工作,您需要使
selectAnimal
generic。您可能认为它应该能够处理联合类型的
animal
输入,但编译器无法正确地对使用依赖于同一联合类型的多个表达式的单个代码块进行类型检查。它丢失了${animal}s
和allAnimals.animals[animal]
之间的 * 相关性 *。前者的类型是"cats" | "dogs"
,后者的类型是{cats: CatState[]} | {dogs: DogState[]}
,通常不能用前者索引后者,因为“如果你有{cats: CatState[]}
,而你用"dogs"
索引呢?“这不可能发生,但编译器无法看到它。TypeScript不能直接以这种方式处理相关的联合体。这就是microsoft/TypeScript#30581的主题。如果您希望单个代码块适用于多种情况,则需要重构类型以使用泛型,如microsoft/TypeScript#47109中所述。下面是您的示例的外观:
AnimalStateMap
是一种基本的键值类型,表示数据结构中的底层关系。然后AnimalData<K>
是一个Map类型,它将s
与键类型的连接编码为template literal type(给出复数gooses
和fishs
🤷 ♂️),并且值类型是预期的动物数组。有一个allSelected
属性。然后你的
Animals
类型显式地写为mapped type overkeyof AnimalStateMap
,这将有助于编译器在我们index into它时看到相关性。最后,
selectAnimal
在K extends keyof AnimalStateMap
中是泛型的,主体类型检查是因为animalPlural
恰好是${K}s
的泛型类型,已知${K}s
是animalData
的键,即AnimalData<K>
。Playground链接到代码
6qfn3psc2#
你应该使用一个类似的变量名(例如
states
)而不是cats
和dogs
。如果不这样做,TypeScript会将cats
和dogs
视为两个不相关的对象。看看这个TypeScriptPlayground。注意,我删除了
PayloadAction
类型,因为OP没有提供它,并在对象解构中添加了一个类型。FooState
也可以从常规类型转换为接口。还请注意,我添加了一个FooStateAnimalEntry
接口。添加此接口可以使代码更有条理,并提高可读性。您可能需要更改
FooStateAnimalEntry.more
的类型,这可能需要使用generics完成。