代码之家  ›  专栏  ›  技术社区  ›  Tyler Rick

如何使函数将返回值类型缩小到特定子类型,而不是返回联合或索引类型

  •  0
  • Tyler Rick  · 技术社区  · 5 年前

    我有一个函数,它返回一个具有相同形状的对象,除了一个可以命名为 value defaultValue ,以价值为准 valueProp . 下面是一个简单的例子:

    type ValueType = 'value' | 'defaultValue'
    function props0(valueProp: ValueType) {
      return {
        [valueProp]: 'value',
        other: 'other',
      }
    }
    

    任何一个 {value, other} {defaultValue, other}

    function expectsDefaultValue({defaultValue, other}) {
      console.log('defaultValue:', defaultValue)
    }
    
    expectsDefaultValue(props0('defaultValue'))
    

    ,它抱怨我试图传递的值是 { [x: string]: string; other: string; } 而不是预期/期望的类型, {默认值,其他} :

    Argument of type '{ [x: string]: string; other: string; }' is not assignable to parameter of type '{ defaultValue: any; other: any; }'.
      Property 'defaultValue' is missing in type '{ [x: string]: string; other: string; }' but required in type '{ defaultValue: any; other: any; }'.ts(2345)
    

    使用索引类型?

    index type . 有什么方法可以将索引类型缩小到预期的类型吗?

    看来我至少应该能 type assertion 告诉 字体说明它实际上是正确的形状,但是没有。。。

    expectsDefaultValue(props0('defaultValue') as PropsUsingDefaultValue)
    // => Conversion of type '{ [x: string]: string; other: string; }' to type
    // 'PropsUsingDefaultValue' may be a mistake because neither type sufficiently
    // overlaps with the other. If this was intentional, convert the expression to
    // 'unknown' first.
    // Property 'defaultValue' is missing in type '{ [x: string]: string; other:
    // string; }' but required in type 'PropsUsingDefaultValue'.
    

    这种索引类型一直存在同样的老问题。“强迫”的唯一方法似乎是这样,这似乎是不可接受的,这就是为什么我在这里寻找更好的解决方案:

    expectsDefaultValue(props0('defaultValue') as unknown as PropsUsingDefaultValue)
    

    不使用索引类型?

    如果索引类型有太多限制(键只能是 string ValueType )...

    如何使此函数通用 索引类型,并且没有任何额外的重复?

    我的下一个尝试是使用 if/else 分支以显式返回形状正确的对象,但这太冗长,重复次数太多,不可接受:

    interface Props {
      other: any
    }
    interface PropsUsingValue extends Props {
      value: any
      other: any
    }
    interface PropsUsingDefaultValue extends Props {
      defaultValue: any
      other: any
    }
    
    function props1(valueProp: ValueType): PropsUsingValue | PropsUsingDefaultValue {
      if (valueProp === 'value') {
        return {
          value: 'value',
          other: 'other',
        } as PropsUsingValue
      } else {
        return {
          defaultValue: 'value',
          other: 'other',
        } as PropsUsingDefaultValue
      }
    }
    

    即使这样我也不能这么做:

    expectsDefaultValue(props1('defaultValue'))
    // => Property 'defaultValue' is missing in type 'PropsUsingValue' but 
    // required in type 'PropsUsingDefaultValue'.ts(2345)
    

    (为什么不在这里推断正确的、特定的返回值类型?)

    但可以做到:

    expectsDefaultValue(props1('defaultValue') as PropsUsingDefaultValue)
    

    PropsUsingValue | PropsUsingDefaultValue )把这里的字体弄混了(或者我遗漏了别的什么。。。?

    也许有办法用 conditional types 为了说明我们只返回其中一种类型而不是那些类型的并集?

    function props2<VT extends ValueType>(valueProp: VT):
      VT extends 'value' ? PropsUsingValue : PropsUsingDefaultValue
    {
      if (valueProp === 'value') {
        return {
          value: 'value',
          other: 'other',
        } as PropsUsingValue
        // => Type 'PropsUsingValue' is not assignable to type 'VT extends "value" ? PropsUsingValue : PropsUsingDefaultValue'.ts(2322)
      } else {
        return {
          defaultValue: 'value',
          other: 'other',
        } as PropsUsingDefaultValue
      }
    }
    

    映射类型?

    因为索引类型似乎有限制(键只能是 一串 number ),也许我们可以尝试映射类型?(但具体如何?)

    泛型的局限性?

    好像我在做类似的事情 https://github.com/microsoft/TypeScript/issues/13995#issuecomment-463445339 ,但显然泛型有一个限制,泛型扩展联合(它 值类型

    我们如何解决这个限制(或者重写以避免遇到它)?

    使用helper函数推断实际类型

    this 也许 吧?

    类型之间的映射表?

    Narrowing a return type from a generic, discriminated union in TypeScript

    也许我下一步会试试。

    0 回复  |  直到 5 年前
        1
  •  2
  •   Tyler Rick    5 年前

    我认为解决办法应该很简单。可以使用函数的泛型签名来捕获参数的实际类型。我们可以用 Record 要创建所需的类型:

    type ValueType = 'value' | 'defaultValue'
    function props0<T extends ValueType>(valueProp: T): Record<T, string> & { other : string}
    function props0(valueProp: ValueType) {
      return {
        [valueProp]: 'value',
        other: 'other',
      }
    }
    
    let { defaultValue, other } = props0('defaultValue') //ok
    let { value } = props0('value') // ok
    

    Play


    避免使用函数重载的变量

    type ValueType = 'value' | 'defaultValue'
    
    type Props<T extends ValueType> =
      Record<T, string> 
      & { other: string }
    
    function props<T extends ValueType>(valueProp: T): Props<T> {
      return {
        [valueProp]: 'value',
        other: 'other',
      } as Props<T>
    }
    

    Play

        2
  •  1
  •   Mon Villalon    5 年前

    type ValueType = 'value' | 'defaultValue'
    
    interface ICommonResult {
      other: string;
    }
    
    interface IValueResult extends ICommonResult {
        value: string;
    }
    
    interface IDefaultValueResult extends ICommonResult {
        defaultValue: string;
    }
    
    type Result = IValueResult | IDefaultValueResult
    
    function props0(valueProp: 'value'): IValueResult
    function props0(valueProp: 'defaultValue'): IDefaultValueResult
    function props0(valueProp: ValueType): Result  {
        const obj = { other: 'other' };
    
        if (valueProp === 'value') {
          return { value: 'value', ...obj }
        }
    
        return { defaultValue: 'value', ...obj }
    
    }
    
    function expectsDefaultValue({defaultValue, other}: IDefaultValueResult) {
      console.log('defaultValue:', defaultValue)
    }
    
    expectsDefaultValue(props0('value')) // Fails
    expectsDefaultValue(props0('defaultValue')) // OK
    

    Link To Playground