TypeScript无法从类型化字符串变量正确推断类型

f45qwnt8  于 2023-06-07  发布在  TypeScript
关注(0)|答案(2)|浏览(179)

我尝试使用一个“自由”创建的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。是否有解决方法,或者根本不可能?

uplii1fm

uplii1fm1#

为了使其工作,您需要使selectAnimalgeneric
您可能认为它应该能够处理联合类型的animal输入,但编译器无法正确地对使用依赖于同一联合类型的多个表达式的单个代码块进行类型检查。它丢失了${animal}sallAnimals.animals[animal]之间的 * 相关性 *。前者的类型是"cats" | "dogs",后者的类型是{cats: CatState[]} | {dogs: DogState[]},通常不能用前者索引后者,因为“如果你有{cats: CatState[]},而你用"dogs"索引呢?“这不可能发生,但编译器无法看到它。TypeScript不能直接以这种方式处理相关的联合体。这就是microsoft/TypeScript#30581的主题。
如果您希望单个代码块适用于多种情况,则需要重构类型以使用泛型,如microsoft/TypeScript#47109中所述。下面是您的示例的外观:

interface AnimalStateMap {
    cat: CatState,
    dog: DogState
}

type AnimalData<K extends keyof AnimalStateMap> =
    { [P in `${K}s`]: AnimalStateMap[K][] & { allSelected: boolean } }

export interface Animals {
    animals: { [K in keyof AnimalStateMap]: AnimalData<K> };
};
    

const selectAnimal = <K extends keyof AnimalStateMap>(
    allAnimals: Animals, animal: K, index: number) => {
    const animalPlural = `${animal}s` as const;
    // const animalPlural: `${K}s`
    const animalData: AnimalData<K> = allAnimals.animals[animal]
    animalData[animalPlural][index].selected = true;
}

AnimalStateMap是一种基本的键值类型,表示数据结构中的底层关系。然后AnimalData<K>是一个Map类型,它将s与键类型的连接编码为template literal type(给出复数goosesfishs🤷‍ ♂️),并且值类型是预期的动物数组。有一个allSelected属性。
然后你的Animals类型显式地写为mapped type over keyof AnimalStateMap,这将有助于编译器在我们index into它时看到相关性。
最后,selectAnimalK extends keyof AnimalStateMap中是泛型的,主体类型检查是因为animalPlural恰好是${K}s的泛型类型,已知${K}sanimalData的键,即AnimalData<K>
Playground链接到代码

6qfn3psc

6qfn3psc2#

你应该使用一个类似的变量名(例如states)而不是catsdogs。如果不这样做,TypeScript会将catsdogs视为两个不相关的对象。
看看这个TypeScriptPlayground。注意,我删除了PayloadAction类型,因为OP没有提供它,并在对象解构中添加了一个类型。
FooState也可以从常规类型转换为接口。还请注意,我添加了一个FooStateAnimalEntry接口。添加此接口可以使代码更有条理,并提高可读性。
您可能需要更改FooStateAnimalEntry.more的类型,这可能需要使用generics完成。

相关问题