【问题标题】:Typescript: tuple with no duplicates打字稿:没有重复的元组
【发布时间】:2022-11-22 20:45:50
【问题描述】:

问题定义

假设我们有一个 React 组件 C 接受属性 PropsProps 有一个名为edges 的字段。 Edges 被定义为长度为 1-4 的元组,由字符串文字 topbottomleftright 组成。

任务:将 edges 参数限制为没有重复的元组。

例如。:

这应该编译得很好:

<C edges={['top', 'bottom']} />

虽然这应该会失败:

<C  edges={['top', 'top']} />

到目前为止我所拥有的


// Our domain types
type Top = 'top';
type Bottom = 'bottom';
type Left = 'left';
type Right = 'right';
type Edge = Top | Bottom | Left | Right;

// A helper types that determines if a certain tuple contains duplicate values
type HasDuplicate<TUPLE> = TUPLE extends [infer FIRST, infer SECOND]
    ? FIRST extends SECOND
        ? SECOND extends FIRST
            ? true
            : false
        : false
    : TUPLE extends [first: infer FIRST, ...rest: infer REST]
    ? Contains<FIRST, REST> extends true
        ? true
        : HasDuplicate<REST>
    : never;

// Just some helper type for convenience
type Contains<X, TUPLE> = TUPLE extends [infer A]
    ? X extends A
        ? A extends X
            ? true
            : false
        : false
    : TUPLE extends [a: infer A, ...rest: infer REST]
    ? X extends A
        ? A extends X
            ? true
            : Contains<X, REST>
        : Contains<X, REST>
    : never;

通过以上我已经可以得到这个:

type DoesNotHaveDuplicates = HasDuplicate<[1, 2, 3]>; // === false
type DoesHaveDuplicates = HasDuplicate<[1, 0, 2, 1]>; // === true

我被困在哪里

假设我们有一个组件 C:


// For simple testing purposes, case of a 3-value tuple
type MockType<ARG> = ARG extends [infer T1, infer T2, infer T3]
    ? HasDuplicate<[T1, T2, T3]> extends true
        ? never
        : [T1, T2, T3]
    : never;

interface Props<T> {
    edges: MockType<T>;
}

function C<T extends Edge[]>(props: Props<T>) {
    return null;
}

上面的工作,但只是这样的:

// this compiles:
<C<[Top, Left, Right]> edges={['top', 'left', 'right']} />

// this does not (as expected):
<C<[Top, Left, Left]> edges={['top', 'left', 'left']} />

我无法弄清楚的是如何摆脱组件实例化中的泛型,并使打字稿根据提供给 edges 属性的值在编译时推断出类型。

【问题讨论】:

  • 如果 this 有效,请告诉我。没有明确的泛型

标签: reactjs typescript tuples


【解决方案1】:

我真的不明白MockType 的意义。所以让我们摆脱它。

相反,使用条件检查 HasDuplicate&lt;T&gt; 是否为 false。如果是,我们可以将edges的类型设置为[...T]

interface Props<T extends Edge[]> {
    edges: HasDuplicate<T> extends false ? [...T] : never;
}

可变元组语法在这里很重要,因为它向编译器提示我们要将 T 推断为元组。否则,T 将被推断为以 Edge 的并集作为其类型的数组。

// these compile
const a = (
  <>
    <C<[Top, Left, Right]> edges={['top', 'left', 'right']} />
    <C edges={['top', 'left', 'right']} />
  </>
)

// these do not compile
const b = (
  <>
    <C<[Top, Left, Left]> edges={['top', 'left', 'left']} />
    <C edges={['top', 'left', 'left']} />
  </>
)

一个站点注释:我们还可以稍微简化您的HasDuplicate 类型。

type HasDuplicate<TUPLE extends any[]> = 
  TUPLE extends [infer L, ...infer R]
    ? L extends R[number]
      ? true
      : HasDuplicate<R>
    : false

Playground

【讨论】:

  • 爱它!工作出色。
【解决方案2】:

替代方法,没有明确的泛型和较少的实用类型。

如果您为edges预定义了允许的字符串,您可以考虑这种方法:

import React from "react"

type Top = 'top';
type Bottom = 'bottom';
type Left = 'left';
type Right = 'right';
type Edge = Top | Bottom | Left | Right;

// https://github.com/microsoft/TypeScript/issues/13298#issuecomment-692864087
type TupleUnion<U extends string, R extends any[] = []> = {
    [S in U]: Exclude<U, S> extends never ? [...R, S?] : TupleUnion<Exclude<U, S>, [...R, S?]>;
}[U];


interface Props {
    edges: TupleUnion<Edge>
}

function C(props: Props) {
    return null;
}

(
    <>
    // this compiles:
        <C edges={['top', 'left', 'right']} />

    // this does not (as expected):
        <C edges={['top', 'left', 'left']} />

    // this compiles:
        <C edges={['top', 'left', 'right']} />

    // this does not
        <C edges={['top', 'left', 'left']} />
    </>
)

Playground

如果你想允许任何字符串,你可以使用这种方法:

import React from "react"

type WithDuplicates<Keys extends string[], Acc extends string[] = []> =
  Keys extends []
  ? Acc
  : Keys extends [infer First extends string, ...infer Tail extends string[]]
  ? First extends Acc[number]
  ? WithDuplicates<Tail, [...Acc, never]>
  : WithDuplicates<Tail, [...Acc, First]>
  : Keys

interface Props<T extends string[]> {
  edges: T
}

function C<E extends string, Edges extends E[]>(props: Props<WithDuplicates<[...Edges]>>) {
  return null;
}

(
  <>
    // this compiles:
    <C edges={['top', 'left', 'right']} />

    // this does not (as expected):
    <C edges={['top', 'left', 'left']} />

    // this compiles:
    <C edges={['top', 'left', 'right']} />

    // this does not
    <C edges={['top', 'left', 'left']} />
  </>
)

playground

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2023-02-22
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多