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
