【问题标题】:Type '"id"' cannot be used to index type '...'类型 \'\"id\"\' 不能用于索引类型 \'...\'
【发布时间】:2022-11-11 15:12:22
【问题描述】:

在下面的代码中,addThingExample2addThing 都有类似的定义。虽然addThingExample2 直接引用联合类型Things,而addThing 引用泛型参数THING extends Thing

PropsMapper<Things>[TYPE]['id'] 有效(无类型错误)。

PropsMapper<THING>[TYPE]['id'] 不起作用(类型错误:Type '"id"' cannot be used to index type 'PropsMapper<THING>[TYPE]'.

有没有办法让这个 addThing 函数编译没有类型错误?

type AllowedThingType = 'Smartphone' | 'Toy' | 'Magazine';

type ThingType<TYPE extends AllowedThingType, PROPS extends Record<string, unknown>> = {type: TYPE, props: PROPS};

type ThingTypes = 
  | ThingType<'Smartphone', {color: 'blue' | 'red', price: number}> 
  | ThingType<'Toy', {size: 'small' | 'medium'}>
  | ThingType<'Magazine', {theme: 'nature' | 'games'}>;

type Thing<TYPE extends ThingTypes['type'] = ThingTypes['type'], ID extends string = string> = {id: ID, type: TYPE };

type Things = Thing<'Smartphone', 'Samsung S21'> | Thing<'Smartphone', 'Google Pixel 7'> | Thing<'Toy', 'Sheriff Woody'>;

// Example 1 - no constraints
function addThingExample1(type: Things['type'], id: Things['id'], props: ThingTypes['props']) { /* ... */ }
// the following code will compile fine, but that's not what I want, because it doesn't enforce constraints between the arguments...
addThingExample1('Smartphone', 'Sheriff Woody', { theme: 'nature' });

// Example 2 - with constraints
type TypeProps = { [K in ThingTypes as K['type']]: { props: K['props'] }};
type PropsMapper<T extends Thing> = { [K in T as K['type']]: {id: K['id'], props: TypeProps[K['type']]['props'] }};

// the following code works as expected, typescript help us to make sure you will pass a consistent combination of type, id and props:
function addThingExample2<TYPE extends keyof PropsMapper<Things>>(type: TYPE, id: PropsMapper<Things>[TYPE]['id'], props: PropsMapper<Things>[TYPE]['props']) { /* ... */ }
addThingExample2('Smartphone', 'Sheriff Woody', { theme: 'nature' }); // It works ==> Type error: Argument of type '"Sheriff Woody"' is not assignable to parameter of type '"Samsung S21" | "Google Pixel 7"'
addThingExample2('Smartphone', 'Google Pixel 7', { color: 'red', price: 10 }); // It works ==> no type error

// Example 3 - dynamic Thing
class ThingsRepository<THING extends Thing> {

  addThing<TYPE extends keyof PropsMapper<THING>>(type: TYPE, id: PropsMapper<THING>[TYPE]['id'], props: PropsMapper<THING>[TYPE]['props']) {
    // This function has a similar signature to addThingExample2, but it uses a class generic param THING instead of using Things type directly... 
    // How can I make this work?
  }
}

// I would like to use this way:
type MyThings =  Thing<'Smartphone', 'Samsung S21'> | Thing<'Smartphone', 'Google Pixel 7'> | Thing<'Toy', 'Sheriff Woody'>;
const myThingsRepo = new ThingsRepository<MyThings>();
myThingsRepo.addThing('Smartphone', 'Sheriff Woody', { theme: 'nature' }); // It works ==> Type error here...
myThingsRepo.addThing('Smartphone', 'Google Pixel 7', {color: 'blue', price: 10}); // It works ==> No type error here...

Link to TS Playground

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    我发现了一个非常肮脏的解决方法......它的行为应该完全相同,但本质上,我们正在欺骗编译器认为我们正在给它正确的密钥(好吧,我们已经是,现在我们只是断言它是)。

    class ThingsRepository<THING extends Thing> {
        addThing<TYPE extends keyof PropsMapper<THING>, T extends keyof PropsMapper<THING>[TYPE] & "id", U extends keyof PropsMapper<THING>[TYPE] & "props">(
            type: TYPE,
            id: PropsMapper<THING>[TYPE][T],
            props: PropsMapper<THING>[TYPE][U]
        ) {
            // ...
        }
    }
    

    Playground

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-11-15
      • 2018-04-01
      • 2017-05-23
      • 2020-02-08
      • 2018-02-13
      • 2023-01-31
      • 2021-10-24
      • 1970-01-01
      相关资源
      最近更新 更多