TypeScript:扩展Array.prototype.join以支持元组

cdmah0mi  于 2023-02-10  发布在  TypeScript
关注(0)|答案(1)|浏览(150)

我想扩展Array.prototype.join,这样如果数组是元组,它会使用分隔符返回一个模板化的字符串类型。我有一个实用程序类型可以做到这一点,但无论我如何尝试,当我尝试使用扩展的Array定义时,this总是被转换为数组。有人知道如何做到这一点吗?
另外,我很想找到一些高级类型的好资源,比如:书籍、文章、视频等
Playground链接
参考代码:

type Throw<ErrorMessage extends string> = `TypeError: ${ ErrorMessage }` & { [ Key in ErrorMessage ]: void }
   
type Join<TupleOrArray, Delimiter> =
  TupleOrArray extends [] ? '' 
  : Delimiter extends string
    ? TupleOrArray extends [infer First, ...infer Rest] | Readonly<[infer First, ...infer Rest]>
      ? First extends string | number | bigint | boolean | null | undefined
        ? Rest extends [] 
          ? `${ First }` 
          : `${ First }${ Delimiter }${ Join<Rest, Delimiter> }` 
        : Throw<'Rest must be an array of strings or empty array'>
      : TupleOrArray extends { toString: () => string }[]
        ? string
        : Throw<'StringTuple is not an array or tuple'>
    : Throw<'Delimiter must be a string'>
  ;

interface Array<T> {
  join<Delimiter>(delimiter: Delimiter): this extends infer TupleOrArray ? Join<TupleOrArray, Delimiter> : never
}

const array = ['a', 'b', 'c']
const tuple = [ 'a', 'b', 'c' ] as const

type JoinedArray = Join<typeof array, '-'>
//   ^? type JoinedArray = string

const joinedArray = array.join('-')
//    ^? const joinedArray: string
// 😎

type JoinedType = Join<typeof tuple, '-'>
//   ^? type JoinedType = 'a-b-c'

const joinedTuple = tuple.join('-')
//    ^? const joinedTuple: string
// 😭

在上面的代码中可以看到,当我希望它是'a-b-c-'时,joinedTuple只是一个string
我错过了什么?

mkshixfv

mkshixfv1#

当你在一个数组文本上使用constAssert时,你会得到一个readonly元组类型:

const tuple = ['a', 'b', 'c'] as const;
// const tuple: readonly ["a", "b", "c"]

在尝试merge中的任何内容之前,询问IntelliSense tuple.join是什么以及它来自哪里是很有用的:

tuple.join;
// (method) ReadonlyArray<"a" | "b" | "c">.join(separator?: string | undefined): string

因此,编译器从ReadonlyArray<T>接口获取join()方法,如此处所声明的。
这意味着如果你想合并join()的重载签名并让它影响tuple,你应该把它放在ReadonlyArray<T>而不是Array<T>中:

interface ReadonlyArray<T> {
  join<D extends string>(delimiter: D): Join<this, D>
}

请注意,我已经修改了该签名,以便将type参数约束为string,这样编译器就知道将其推断为字符串类型,而不仅仅是microsoft/TypeScript#10676中所描述的string
而且我看不出有什么理由要把this复制到一个新的类型参数中,所以我把它省略了,如果你需要这样做的话,我想这是可以的,但是它没有在示例代码中显示出来。
让我们来测试一下:

const joinedTuple = tuple.join('-');
// const joinedTuple: "a-b-c"

看起来不错。
在实践中,你可能不会接触到很多不是readonly的字符串常量元组,但是如果你还想为可变数组重载join(),你也必须在这里显式地这样做:

interface Array<T> {
  join<D extends string>(delimiter: D): Join<this, D>
}

你可以看到它的行动:

const mutableTuple: ["z", "y", "x"] = ["z", "y", "x"];
const joinedMutableTuple = mutableTuple.join(", ");
// const joinedMutableTuple: "z, y, x"

Playground代码链接

相关问题