【问题标题】:Wrong type inference of higher-order function argument type高阶函数参数类型的错误类型推断
【发布时间】:2020-01-24 18:41:28
【问题描述】:

假设我想编写一个函数,它接受某种类型 T 的对象和另一个值,该类型 P 应该以某种方式受 T 限制,例如 P 应该是 T 的键数组。

我可以很容易地写出来:

function bar<T, P extends keyof T>(obj: T, p: P[]) {
  // use p to index obj somehow
  return obj;
}

bar({ a: 1, b: 'foo' }, ['a']); // Ok
bar({ a: 1, b: 'foo' }, ['a', 'b']); // Ok
bar({ a: 1, b: 'foo' }, ['a', 'b', 'c']); // Error: 'c' is not valid key

想象一下,然后我想将该函数用作高阶方法的参数,它应该与第二个参数 arg 一起接受它,然后用 thisarg 调用它:

class Indexed {
  constructor(public a: number = 1) {}
  public app<P>(f: (obj: this, arg: P) => this, arg: P) {
    return f(this, arg);
  }
}

const a = new Indexed().app(bar, ['a']); // Error, `Type 'string' is not assignable to type '"a" | "app"'.`
const a = new Indexed().app(bar, ['wtf']); // The same

如果我直接使用bar,一切都会按预期进行:

bar(new Indexed(), ['a']); // Ok
bar(new Indexed(), ['wtf']); // Err, as expected

Playground

问题是:如何编写app 使其接受/拒绝参数的方式与bar 相同?

请注意,一般我不知道 bar 先验的限制,所以我不能用与 bar 相同的界限来限制 P

【问题讨论】:

    标签: typescript types type-inference


    【解决方案1】:

    我认为这只是 TypeScript 将 ["foo","bar"] 扩展为 string[] 的一种情况,因为它没有意识到您需要该类型来保持字符串文字元组 ["foo", "bar"](或至少是字符串文字数组 @ 987654326@)。在您的 bar() 函数中,P 被限制为 keyof 任何提示编译器不要将字符串文字扩展为字符串,但在 Indexed.app() 中不存在 P 的此类提示。

    您需要想出一种方法来修改Indexed.app() 签名,以暗示P 应该在可能的情况下以狭窄的方式推断而无需实际限制它(因为您不知道P 将如您所说),或者您需要想出一种方法来提示/指定P 在您调用 Indexed.app() 时应该是狭窄的。


    修改app() 的签名来做到这一点目前需要一些奇怪的技巧,除非这个changes,否则它看起来像这样:

    type Narrowable =
      | string
      | number
      | boolean
      | symbol
      | object
      | undefined
      | void
      | null
      | {};
    
    class Indexed {
      constructor(public a: number = 1) {}
      public app<
        N extends Narrowable,
        P extends N | [] | { [k: string]: N | P | [] }
      >(f: (obj: this, arg: P) => this, arg: P) {
        return f(this, arg);
      }
    }
    
    const a = new Indexed().app(bar, ["a"]); // okay
    const b = new Indexed().app(bar, ["wtf"]); // error "wtf" not assignable to "a"|"app"
    

    如果调用者记得这样做,调用站点的提示就不会那么难看:

    class Indexed {
      constructor(public a: number = 1) {}
      public app<P>(f: (obj: this, arg: P) => this, arg: P) {
        return f(this, arg);
      }
    }
    const a = new Indexed().app(bar, ["a" as "a"]); // okay
    const b = new Indexed().app(bar, ["wtf" as "wtf"]); // error "wtf" not assignable to "a"|"app"
    

    或者你也可以忘记提示,自己手动指定类型参数:

    const c = new Indexed().app<["a"]>(bar, ["a"]); // okay
    const d = new Indexed().app<["wtf"]>(bar, ["wtf"]); // error "wtf" not assignable to "a"|"app"
    

    好的,希望其中一个对您有所帮助。祝你好运!

    Link to code

    【讨论】:

      猜你喜欢
      • 2012-11-21
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2022-01-11
      • 2013-11-10
      相关资源
      最近更新 更多