【问题标题】:Inferring parameter of method of generic class from union type从联合类型推断泛型类方法的参数
【发布时间】:2021-07-28 17:22:45
【问题描述】:

我有这个用例,我有一个泛型类和一个包含泛型类的多个成员的容器类。我想使用setViaFunction 来设置容器类的成员,而不取决于它们的类型。但是我收到以下错误:Argument of type 'string | boolean' is not assignable to parameter of type 'never'. Type 'string' is not assignable to type 'never'.(2345)typescript playground link

另请注意,setAnyMember 手动执行相同的操作,但并非总是可以执行不会产生该错误。

还有其他方法可以在 typescript 中实现此功能吗?

class GenericClass<T> {
    public value: T
    constructor(_value: T){
        this.value = _value
    }
    setValue(_value:T){
        this.value = _value
    }
}

class ContainerClass {
    sthString = new GenericClass('sdad')

    sthBoolean = new GenericClass(true)

    setAnyMember = (field: 'sthString' | 'sthBoolean', val: string | boolean) => {
    this[field].value = val
    }
    setViaFunction = (field: 'sthString' | 'sthBoolean', val: string | boolean) => {
    this[field].setValue(val)
    }
}

【问题讨论】:

    标签: typescript typescript-generics


    【解决方案1】:

    我猜赋值不会因为类型匹配而失败

    this[field].valuestring | boolean 类型 val 也是。

    如果您尝试调用 this[field].setValue(val),则 typescript 不够聪明,无法自行找出类型:/。

    我想不出一种聪明的方法来帮助 typescript 自动选择正确的类型。

    我会尝试通过不传递字段并通过 val 的类型调用正确的方法来解决这个问题

    
    class ContainerClass {
        sthString = new GenericClass('sdad')
    
        sthBoolean = new GenericClass(true)
    
        setAnyMember = (field: 'sthString' | 'sthBoolean', val: string | boolean) => {
        this[field].value = val
        }
        setViaFunction = (val: string | boolean) => {
            if (typeof val === "string"){
                this.sthString.setValue(val)
            } else {
                this.sthBoolean.setValue(val)
            }
        }
    }
    

    【讨论】:

      【解决方案2】:

      让我们定义我们的 util 类型:

      class GenericClass<T> {
          public value: T
          constructor(_value: T) {
              this.value = _value
          }
      
          setValue(_value: T) {
              this.value = _value
          }
      }
      
      // infer generic parameter from GenericClass
      type GetGeneric<T> = T extends GenericClass<infer G> ? G : never
      {
          type Test = GetGeneric<GenericClass<boolean>> // boolean
      }
      
      // infer all props which are instances of GenericClass
      type GetProps<T> = {
          [Prop in keyof T]: T[Prop] extends GenericClass<any> ? Prop : never
      }[keyof T]
      {
          type Test = GetProps<ContainerClass> // "sthString" | "sthBoolean"
      }
      
      // infer correct type of GenericClass constructor argument
      type GetValues<Obj, Prop> = Prop extends GetProps<Obj> ? GetGeneric<Obj[Prop]> : never
      {
          type Test = GetValues<ContainerClass, 'sthString'> // string
      }
      
      class ContainerClass {
          sthString = new GenericClass('sdad')
      
          sthBoolean = new GenericClass(true)
          /**
           * Field is one of "sthString" | "sthBoolean"
           */
          setAnyMember<Field extends GetProps<this>>(
              /**
               * this is a Record with Field keys and appropriate GenericClass instances
               */
              this: Record<Field, GenericClass<GetValues<this, Field>>>,
              field: Field, // "sthString" | "sthBoolean"
              val: GetValues<this, Field> // get value by Field key
          ) {
              this[field].setValue(val) // ok
      
          }
      }
      const x = new ContainerClass();
      x.setAnyMember('sthBoolean', true) // ok
      x.setAnyMember('sthString', 'str') // ok
      
      x.setAnyMember('sthBoolean', 32) // expected error
      x.setAnyMember('sthString', false) // expected error
      

      Playground

      我用this输入setAnyMember给TS一些线索。 我还让非法状态无法表示,你不能将无效参数传递给setAnyMember

      【讨论】:

      • 我喜欢这种方法,因为它确实解决了我的问题,但我不喜欢它,因为它投射了这个。 (this as Record&lt;'sthString' | 'sthBoolean', any&gt; )[field].setValue(val) 几乎相当于这个,虽然它有效,但我希望有更好的解决方案。
      • 当然没问题
      • @meougal 请记住,TS 不跟踪突变。见我的文章catchts.com/mutations
      猜你喜欢
      • 2020-09-30
      • 2012-02-20
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-04-10
      • 1970-01-01
      • 1970-01-01
      • 2022-01-07
      相关资源
      最近更新 更多