首页 > 解决方案 > Typescript如何在父函数上使用`unknown`类型来确定子函数的类型?

问题描述

我希望能够确定myValue对应的接口。鉴于以下情况,我如何记录ab取决于返回值testZero()

export interface ITest {
  id: string
  isLatest: boolean
  createdAt: number
  updatedAt: number
}

export interface ITestTwo {
  id: string
  isLatest: boolean
  createdAt: string  // This value was changed for this example
  updatedAt: number
}

function testZero(): unknown {
    return {
        id: '123',
        isLatest: true,
        createdAt: '123',
        updatedAt: 456
    }
}

function testOne(): ITest | ITestTwo {
    const myValue = testZero()

    // THE FOLLOWING `IF` STATEMENT DOES NOT WORK AND IS WHAT I AM TRYING TO SOLVE
    if (typeof t === typeof ITest) {
        console.log('a')
    } else if (typeof myValue === typeof ITestTwo) {
        console.log('b')
    }
}

testOne()

标签: typescripttypesinterface

解决方案


您将需要编写运行时代码来检查值是否符合接口。当 TypeScript 代码编译为 JavaScript 时,TypeScript 接口本身会被删除。TypeScript 类型系统旨在在运行时描述类型;它不影响运行时间。所以ITestITestTwo不会在附近检查。

此外,运行时运typeof算符将检查其操作数的类型并返回一小组字符串值中的一个;typeof myValue就您而言,无论如何,您都可以期望"object"。所以你不能typeof用来检查myValue自己。充其量你需要检查myValue的属性并使用typeof它们。


一种方法是编写一些类型保护函数来以编译器识别为类型检查的方式表示此运行时检查。对于“简单”对象类型,您可以使用运行时运typeof算符检查每个属性,您可以编写一个通用检查函数,然后根据您的需要对其进行专门化。广义函数:

type TypeofMapping = {
  string: string;
  boolean: boolean;
  number: number;
  object: object;
  undefined: undefined;
}
const simpleObjGuard = <T extends { [K in keyof T]: keyof TypeofMapping }>
  (obj: T) => (x: any): x is { [K in keyof T]: TypeofMapping[T[K]] } => typeof x === "object" && x &&
    (Object.keys(obj) as Array<keyof T>).every(k => k in x && typeof x[k] === obj[k]);

然后您可以将其专门用于ITestand ITestTwo

const isITest: (x: any) => x is ITest =
  simpleObjGuard({ id: "string", isLatest: "boolean", createdAt: "number", updatedAt: "number" });

const isITestTwo: (x: any) => x is ITestTwo =
  simpleObjGuard({ id: "string", isLatest: "boolean", createdAt: "string", updatedAt: "number" });

所以该isITest()函数将检查其参数是否与 兼容ITest,并且该isITestTwo()函数将检查其参数是否与 兼容ITestTwo。在任何一种情况下,编译器都会将true结果解释为可以将参数从 缩小unknown到您正在检查的类型的证据:

function testOne(): ITest | ITestTwo {
  const myValue = testZero()
  if (isITest(myValue)) {
    console.log("a "+myValue.createdAt.toFixed())
    return myValue;
  } else if (isITestTwo(myValue)) {
    console.log("b "+myValue.createdAt.toUpperCase())
    return myValue;
  }
  throw new Error("Guess it wasn't one of those");
}

console.log()return行不会导致编译器错误的事实表明编译器将myValue其视为缩小类型。在运行时,您可以检查它是否也可以工作:

testOne() // b 123

那里有一些库可以为你编写这些类型保护函数......我认为io-ts它可以做到这一点,并以一种与 TypeScript 编译器很好地配合的方式制作序列化/反序列化代码。或者你可以自己写,就像simpleObjGuard上面一样。

好的,希望有帮助;祝你好运!

Playground 代码链接


推荐阅读