首页 > 解决方案 > 推断通用的“this”类型

问题描述

这个问题之后,我现在尝试使用扩展接口类型的显式this 参数IModel创建函数:

// interface for Model class

interface IModel {
    state: {}
}

// based on answer to this question https://stackoverflow.com/q/59895071/374328
// now functions will be bound to model instance, so need to specify 'this' parameter as Model

type SingleArgFunction<Model extends IModel, A> = (this: Model, x: A) => A;
type ArrayedReturnFunction<Model extends IModel, A> = (this: Model, x: A) => A[];

type SingleArgFunctionObject<Model extends IModel, AS extends object> = {
    [K in keyof AS]: SingleArgFunction<Model, AS[K]>
}

type ArrayedReturnFunctionObject<Model extends IModel, AS extends object> = {
    [K in keyof AS]: ArrayedReturnFunction<Model, AS[K]>
}

function makeArrayed<Model extends IModel, A>(f: SingleArgFunction<Model, A>): ArrayedReturnFunction<Model, A> {
    return function (x) {
        return [f.call(this, x)];
    }
}

function makeArrayedAll<Model extends IModel, AS extends object>(
    fs: SingleArgFunctionObject<Model, AS>
): ArrayedReturnFunctionObject<Model, AS> {
    const result = {} as ArrayedReturnFunctionObject<Model, AS>;

    (Object.keys(fs) as (keyof AS)[]).forEach(function<K extends keyof AS>(key: K) {
        result[key] = makeArrayed(fs[key]);
    })
    return result;
}

示例模型和类型定义:

interface MyModel extends IModel {
    state: {
        x: number;
    }
}

interface SingleArgFunctions {
    foo: SingleArgFunction<MyModel, number>
}

interface ArrayedReturnFunctions {
    foo: ArrayedReturnFunction<MyModel, number>;
}

直接创建一个对象是ArrayedReturnFunctions可以的,this被推断为类型MyModel

const arrayedReturnFunctions1: ArrayedReturnFunctions = {
    foo(x) {
        return [x + this.state.x]; // ok
    }
}

或者,通过应用于makeArrayed单个函数来创建对象也可以:

const arrayedReturnFunctions2: ArrayedReturnFunctions = {
    foo: makeArrayed(function (x) {
        return x + this.state.x; // ok
    })
}

但是, usingmakeArrayedAll不起作用 -this被推断为 type IModel

const arrayedReturnFunctions3: ArrayedReturnFunctions = makeArrayedAll({
    foo(x) {
        return x + this.state.x; // error - property x does not exist on type {}
    }
})

即使创建一个类型的对象SingleArgFunctions然后将其传递给makeArrayedAll也不起作用:

const singleArgFunctions: SingleArgFunctions = {
    foo(x) {
        return this.state.x + x;
    }
}

const arrayedReturnFunctions4 = makeArrayedAll(singleArgFunctions); // error - IModel is not assignable to type MyModel 

为什么在使用时Model没有推断出类型?MyModelmakeArrayedAll

操场

标签: typescriptmapped-types

解决方案


对我来说,这看起来像是您希望arrayedReturnFunctions分配输出的变量的类型makeArrayedAll()将为输入提供足够的上下文类型,以便推断makeArrayedAll()输入方法的上下文......但它没有发生。this在这种情况下,上下文推理不会发生,我并不感到惊讶,但我对究竟为什么或如何强制它发生没有很好的答案。

现在我唯一的建议是注意类型推断往往在“向前”方向上工作得更好。也就是说,泛型函数的类型参数更容易从该函数的输入推断出来,而不是从函数的预期输出类型推断出来。如果失败,您总是可以手动指定泛型类型参数。

这是手动指定模型类型参数并让编译器使用currying推断其余部分的一种方法:

const makeArrayedAllFor = <M extends IModel>() => <AS extends object>(
  fs: SingleArgFunctionObject<M, AS>) => makeArrayedAll(fs);

const arrayedReturnFunctions3: ArrayedReturnFunctions = makeArrayedAllFor<MyModel>()({
    foo(x) {
        return x + this.state.x;
    }
})

const arrayedReturnFunctions4 = makeArrayedAllFor<MyModel>()(singleArgFunctions);

这现在有效,虽然它很麻烦。这是我目前能想到的最好的;也许有人有其他想法?哦,好吧,祝你好运!

游乐场链接


推荐阅读