【问题标题】:Typescript Conditional Date/string Return Type打字稿条件日期/字符串返回类型
【发布时间】:2021-10-22 23:00:36
【问题描述】:

如何根据 value 参数类型应用 stringDate 的条件函数返回类型?

由于以下错误,强制键入断言函数调用<Date>toggleDateString(stringValue)

Argument of type 'string | Date' is not assignable to parameter of type 'Date'. Type 'string' is not assignable to type 'Date'.

const toggleDateString = (value: Date | string) => {
    if (typeof value === 'string') {
        ...
        return some-Date
    } else {
        ...
        return some-string
    }
}

【问题讨论】:

  • 这段代码遇到了什么问题?
  • @brk 更新了我遇到的问题的帖子。
  • 您是否尝试根据参数类型提供返回类型信息? (即 f(string): date 和 f(date): string)?我不认为编译器可以通过单个函数实现来处理这个问题。你可以改用重载函数吗?
  • this 是否满足您的需求?箭头函数比函数语句更烦人,以使重载或带有条件返回的泛型类型,在任何一种情况下,您都必须对实现类型负责,但至少从调用端它会起作用。如果您想为此写下答案,或者有什么对您不适用的地方,请告诉我。
  • 是的,它是一个generic 函数,它的返回类型是conditional type。我现在得走了,所以有人可能会在我到达之前回答这个问题;如果是这样,希望他们会指向microsoft/TypeScript#33912,这个问题要求更好地支持这些功能......现在你几乎需要一个type assertion 来让实现工作。

标签: javascript typescript types


【解决方案1】:

cmets 中的解决方案很好,尽管在这种情况下泛型似乎使用大炮杀死苍蝇。我相信将这个函数从箭头转换为普通函数会更容易更好(无论如何你似乎没有在这里使用this关键字所以这不是问题)并使用重载

function toggleDateString(value: Date): string
function toggleDateString(value: string): Date
function toggleDateString(value: string | Date): string | Date {
    if (typeof value === 'string') {
        return Date.parse(value) // or whatever
    } else {
        return value.toString() // or whatever
    }
} 

【讨论】:

    【解决方案2】:

    TypeScript 中有两个功能可让您定义返回类型取决于参数类型的函数:overloaded 函数和generic 函数。他们都有警告。


    重载函数

    重载函数有多个调用签名,当您调用该函数时,编译器会根据输入依次检查每个调用签名来选择哪个调用签名。这是toggleDateString 的重载调用签名类型:

    declare const toggleDateString: {
      (value: Date): string;
      (value: string): Date;
    }
    
    toggleDateString(new Date()).toUpperCase(); // string
    toggleDateString("hello").getFullYear() // Date
    

    重载函数注意事项:

    • 通过函数语句实现重载函数时,先声明每个调用签名,然后提供实现:

        function toggleDateString(value: Date): string;
        function toggleDateString(value: string): Date;
        function toggleDateString(value: Date | string) {
          if (typeof value === 'string') {
            return someDate;
          } else {
            return someString;
          }
        }
      

      编译器会进行一些检查以确保实现与调用签名的组合并非完全不兼容,但它允许一些不安全的实现。它只是并没有真正尝试验证实现是否正确。见microsoft/TypeScript#13235。因此,您需要在重载的函数实现语句中小心您正在做正确的事情。例如,下面的编译没有错误:

        function toggleDateString(value: Date): string;
        function toggleDateString(value: string): Date;
        function toggleDateString(value: Date | string) {
          if (typeof value !== 'string') { // <-- oops
            return someDate;
          } else {
            return someString;
          }
        }
      
    • 当您尝试通过 表达式(如 function 表达式或箭头函数)实现重载函数时,您会遇到相反的问题:编译器仍然没有真正尝试验证实现是正确的,但现在它不再允许不安全的实现,而是警告安全的实现。见microsoft/TypeScript#38622。例如:

      const toggleDateString: { // error!
        //  ~~~~~~~~~~~~~~~~ <--
        // Type '(value: Date | String) => string | Date' is not assignable to type 
        // '{ (value: Date): string; (value: string): Date; }'.
          (value: Date): string;
          (value: string): Date;
        } = (value: Date | String) => {
          if (typeof value === 'string') {
            return someDate;
          } else {
            return someString;
          }
        }
      

      因此,为了在那里继续,您需要通过 type assertion 之类的方式抑制错误:

        const toggleDateStringAssert = ((value: Date | String) => {
          if (typeof value === 'string') {
            return someDate;
          } else {
            return someString;
          }
        }) as {
          (value: Date): string;
          (value: string): Date;
        }; // no error now
      

      这会让你重新开始允许不安全的事情(如果你再次将 === 更改为 !==,你会看到)。

    • 调用重载函数时,编译器一次只允许调用其中一个调用签名。它不会“组合”调用签名;见microsoft/TypeScript#41407。如果编译器无法确定调用哪个调用签名,则会出现错误:

        toggleDateString(new Date()); // okay
        toggleDateString("someString"); // okay
        toggleDateString(Math.random() < 0.5 ? new Date() : "someString"); // error!
        // ------------> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
        // No overload matches this call.
        function oops<T extends Date | string>(t: T) {
          toggleDateString(t); // error!
          // No overload matches this call.
        }
      
    • 在类型系统中操作或检查重载函数的类型时,编译器通常会假装只存在第一个或最后一个调用签名。请参阅microsoft/TypeScript#26591 了解更多信息。这有时会产生奇怪的效果:

        type OutputOfToggleDateString = ReturnType<typeof toggleDateString>
        // type OutputOfToggleDateString = Date // <-- just Date?!
      

    通用函数

    对于泛型函数,您只有一个调用签名,但有一个泛型类型参数,该参数将在调用函数时根据输入推断。您需要根据泛型类型参数来表达所需的返回类型。在一般情况下,这可能需要conditional types。这是一个实现:

    const toggleDateString = <T extends Date | string>(value: T): T extends Date ? string : Date => {
      if (typeof value === 'string') {
        return someDate as any;
      } else {
        return someString as any;
      }
    }
    

    您可以在更多情况下调用这些函数,因为只有一个调用签名:

    toggleDateString(new Date()); // okay, string
    toggleDateString("someString"); // okay, Date
    toggleDateString(Math.random() < 0.5 ? new Date() : "someString"); // okay, string | Date
    function okay<T extends Date | string>(t: T) {
      toggleDateString(t); // okay, T extends Date ? string : Date
    }
    

    而且编译器在类型系统中处理这些类型的方面要好一些:

    type OutputOfToggleDateString = ReturnType<typeof toggleDateString>
    // type OutputOfToggleDateString = string | Date
    

    带有条件返回类型警告的泛型函数:

    • 您可能已经注意到,上面的函数在实现中使用了类型断言as any。和重载一样,编译器确实无法验证函数实现是否满足调用签名:

      const togDateStringUgh = <T extends Date | string>(value: T): T extends Date ? string : Date => {
        if (typeof value === 'string') {
          return someDate; // error! 'Date' is not assignable to 'T extends Date ? string : Date'
        } else {
          return someString; // error! 'string' is not assignable to 'T extends Date ? string : Date'
        }
      }
      

      请参阅microsoft/TypeScript#33912 了解更多信息。现在你需要某种类型断言来防止错误。

    • 泛型条件类型比重载看起来有点奇怪,也更难解释。


    我个人倾向于避免重载并更喜欢泛型函数,但这是一个见仁见智的问题。无论哪种方式都可以。

    Playground link to code

    【讨论】:

      猜你喜欢
      • 2021-09-18
      • 1970-01-01
      • 2021-09-17
      • 2018-01-11
      • 2022-07-07
      • 2021-08-25
      • 2019-05-03
      • 2020-06-29
      • 2019-10-25
      相关资源
      最近更新 更多