在打字稿中使用扩展类型进行类型推断

时间:2019-12-30 14:34:00

标签: typescript typescript-typings

我有一个工厂,应该根据提供的枚举键返回一个函数。 这是在切换情况下完成的,那里的所有功能都有不同的有效负载。

我要实现的目标是为该函数提供此类输入,以隐式验证所有有效负载类型和工厂的返回类型。参见下面的示例:

// the list of keys that are used for creation
enum keys {
    key1 = 1,
    key2 = 2
}
// here are interfaces, that tie each key with payload, that should be provided.
// Actually, payload is way more complex (another interfaces) but let's keep it simple
interface Test1 {
    key: keys.key1;
    payload: string;
}
interface Test2 {
    key: keys.key2;
    payload: number;
}

// this is the type, that comes to the function below
type tests = Test1 | Test2;


interface ReturnTypeOfTest1 { returnedObject1: number; }
interface ReturnTypeOfTest2 { returnedObject2: string; }
// this is how I'm configuring what should be returned depending on the key 
// the return type is set to "ResourceModel", which infers a key from a function payload
// and identifies what exactly it should return
interface ModelOfTest {
    [keys.key1]: ReturnTypeOfTest1;
    [keys.key2]: ReturnTypeOfTest2;
}
type ResourceModel<R extends keys> = ModelOfTest[R];


ResourceModel类型是根据另一个stackoverflow问题Typescript: Return type of function based on input value (enum)

创建的

使用上面的类型,我可以验证有效负载的类型,但可以不验证返回类型:


function getTest(t: tests): any{
    switch (t.key) {
        case keys.key1: {
            const data = t.payload; // is string
            return {returnedObject1: 123 };
        } case keys.key2: {
            const data = t.payload; // is number
            return {returnedObject2: '123' };

        }
    }
}
getTest({key: keys.key1, payload: '1' }); // ReturnTypeOfTest1 | ReturnTypeOfTest2

或者获得适当的返回类型,但是在开关盒内放一个验证:

function getTest<T extends tests>(t: T): ResourceModel<T['key']> {
    switch (t.key) {
        case keys.key1: {
            const d = t.payload; // string | number
            return {returnedObject1: 123}; // also an error here, because it wants me to return both ReturnTypeOfTest1 & ReturnTypeOfTest2
        } case keys.key2: {
            const d = t.payload; // string | number
            return null;

        }
    }
}
 getTest({key: keys.key2, payload: 1 }); // ReturnTypeOfTest2

是否可以正确键入此内容?非常感谢您提供任何帮助。

1 个答案:

答案 0 :(得分:1)

这本质上是missing feature in TypeScript (see microsoft/TypeScript#13995);编译器无法很好地说明未指定的泛型类型参数,例如TgetTest()的实现内部。当您呼叫 getTest()时,将指定T,然后正确计算返回类型。但是,当您实现 getTest()时,T仍然是一个未指定/无法解析的类型参数,编译器实际上会放弃。具体来说,它不会尝试使用control-flow-based type analysis根据T extends Test1 | Test2的值将Test1缩小为Test2t.key。这意味着目前,这样的通用类型签名对函数调用者比对函数实现者有用。

也许有一天,这种语言会找到解决方案。目前,有解决方法。在这种情况下,我最常使用的方法是向该函数添加一个overload signature。让调用者看到对他们有用的通用重载签名,而实现则看到对它有用的非通用联合类型签名:

// call signature, seen by callers
function getTest<T extends tests>(t: T): ResourceModel<T['key']>;
// implementation signature, seen only by implementation
function getTest(t: tests): ResourceModel<keys> {
  switch (t.key) {
    case keys.key1: {
      const d = t.payload; // string 
      const ret: ResourceModel<keys.key1> = { returnedObject1: 123 };
      return ret;
    } case keys.key2: {
      const d = t.payload; // number
      const ret: ResourceModel<keys.key2> = { returnedObject2: '123' };
      return ret;
    }
  }
}

这仍然不是真正的类型安全;如果您切换return语句,使ReturnTypeOfTest1返回Test2,反之亦然,以上内容将不会引起您的抱怨。但这至少是目前为止我们在这里能做的最好的,所以您在执行时需要小心。

让我们确保通话能够按需进行:

getTest({ key: keys.key1, payload: "1" }); // ReturnTypeOfTest1
getTest({ key: keys.key2, payload: 1 }); // ReturnTypeOfTest2

看起来不错。无论如何,我希望这会有所帮助;祝你好运!

Playground Link to code