正确键入字符串arrayiffy函数

Properly type the string arrayiffy function

我正在尝试为 arrayiffy 函数设置正确的类型。它的API如下:

基本类型签名重载有效,但“任何”都不是最优的:

function arrayiffy(something: string): [string];
function arrayiffy(something: string): [];
function arrayiffy(something: any): any {
  if (typeof something === "string") {
    if (something.length) {
      return [something];
    }
    return [];
  }
  return something;
}

我尝试设置泛型但失败了 (TS playground link):

type NonString<T> = T extends string ? never : T;

function arrayiffy(something: string): [string];
function arrayiffy(something: string): [];
function arrayiffy<Type>(something: NonString<Type>): Type {
  if (typeof something === "string") {
    if (something.length) {
      return [something];
    }
    return [];
  }
  return something;
}

你会如何处理这个问题?谢谢。

type StringInABox<T> =
  T extends '' ? [] :
  string extends T ? [] | [string] :
  T extends string ? [T] :
  T;

function arrayiffy<T>(something: T): StringInABox<T>;
function arrayiffy<T>(something: T): [] | [string] | T {
  if (typeof something !== 'string') return something;
  if (something.length) return [something];
  return [];
}

See in Playground

function arrayiffy<T>(something: T): [] | [string] | T签名只说你会得到这三个东西之一,[][string]T。它没有指定什么时候你会得到每个,而且如果你传入一个已知的字符串值,它并不表示你传入的值将是你找到的字符串数组里面。不过,这是一种相对 type-safe 编写此函数签名的方式——比 (something: any) => any 负载更好。

重载签名 arrayiffy<T>(something: T): StringInABox<T> 确实保证了另一个签名丢失。 StringInABox 条件类型检查 known-to-be-empty 字符串,未知的 string 可能为空也可能不为空(因此 return 值为 [] | [string]), known-and-not-empty string 直接放入数组 ([T]) 或其他任何东西,它只是 return 本身。

值得注意的是 string extends T 并不完美——如果你有一个 string & SomethingElse,它会失败,然后转到 T extends string,它会通过。结果是 [string & SomethingElse],如果对象的 string 部分具有运行时值 ''(如果它在编译时已知为 '' & SomethingElse,则不问题,那将属于 T extends '' 桶)。

我们使用此重载是因为 Typescript 不会根据我们对输入类型的值(此处为 T)使用的运行时检查来缩小条件类型(例如 StringInABox<T>)的范围。这意味着即使我们知道,Typescript 也知道 something 是一个 string,Typescript 并不知道 Tstring。就 Typescript 而言,即使我们弄清楚 something 是什么,T 仍然是任何东西。这意味着它无法验证 [something] 是否有效 StringInABox<T>,并会抛出错误。我们要么转换所有 return 值,要么使用重载。我选择了一个重载,因为它是 cleaner-looking,但有一个论点是,在转换时最好是丑陋和明确的(这实际上就是 TS 中的重载)。