Pesquisa de site

typescript - Array que requer que apenas um elemento seja de um certo tipo


Isso acabou de ocorrer-me enquanto eu estava brincando com o design da API na minha cabeça. Resumindo, existe uma maneira de definir um tipo assim?

type SpecialArray<Unique, Bland> = [...Bland[], Unique, ...Bland[]];

Mas isso, claro, me dá "Um elemento rest não pode seguir outro elemento rest".

O mais próximo que consigo chegar é:

type SpecialArray<Unique, Bland> = [...Bland[], Unique] | [Unique, ...Bland[]];

o que só permite que o elemento "único" esteja primeiro ou último no array. Note que isso implica que o array também deve conter pelo menos um elemento (o único).

Sim, eu vi Typescript: apenas permita uma ocorrência de um valor em um array, mas as soluções não funcionam para qualquer comprimento, e não consegui encontrar nenhum outro recurso sobre esse problema. Estou ciente de que posso escrever uma função auxiliar para impor/inferir o tipo para mim, assim:

type Count<A extends readonly any[], T, Counter extends any[] = []> = A extends [infer H, ...infer R] ? [H] extends [T] ? Count<R, T, [...Counter, H]> : Count<R, T, Counter> : Counter;

type IsSpecial<A extends readonly any[], Unique, Bland> =
    Count<A, Unique>["length"] extends 1
        ? A[number] extends Unique | Bland
            ? A
            : never
        : never;

function helper<A extends readonly any[]>(array: IsSpecial<[...A], string, number>) { return array; }

O que eu gostaria é de um tipo que eu possa usar assim, em vez disso:

const checked: SpecialArray<string, number> = [...];

É possível? Aceitarei uma solução para SpecialArray<Unique, Bland>, uma alternativa diferente da minha ou que melhore a minha de alguma forma, ou simplesmente um "não" e a explicação de por que não é possível.

Crédito parcial (80% 😉): implemente SpecialArray<Unique, Bland, MaxLength>, que funciona até o comprimento dado (algum tipo de gerador de permutações).

Por favor, note que eu quero algum tipo de verificação em tempo de compilação.

Resposta #1

Criar um gerador de permutações para tuplas de pequeno comprimento, como você sugeriu, é bastante simples se você estiver usando o truque do "contador":

type SpecialArray<U, B, N extends number, R extends any[] = [U]> =
    R["length"] extends N
        ? R
        : R | SpecialArray<U, B, N, [...R, B]> | SpecialArray<U, B, N, [B, ...R]>
;

/**************************/

type Test = SpecialArray<string, number, 5>
const success1: Test = [42, 18, 'foo', 5];
const success2: Test = ['foo', 42]

const error1: Test = [42, 18, 5];               // No `Unique` element
const error2: Test = [42, 18, 'foo', 5, 'bar']; // More than one `Unique` element
const error3: Test = [42, 18, 'foo', 5, 3, 7];  // Too many elements

Playground TS

Resposta #2

type PrevIdx = [ never, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10,
    11, 12, 13, 14, 15, 16, 17, 18, 19, 20, ...0[] ];

export type PrevNum<D extends number> = PrevIdx[D];

type SpecialArray<T extends any[], Unique, D extends number = 10> =
  [ PrevNum<D> ] extends [ never ] ? never
    : T[D] extends Unique ? T
        : SpecialArray<T, Unique, PrevNum<D>>;

uso

type ThingWithThing = SpecialArray<["hello", "world"], "hello">;
> ["hello", "world"]
type ThingWithoutThing = SpecialArray<["hello", "other world"], "world">
> never

Artigos relacionados