Pesquisa de site

typescript - Tipo obrigatório mas possivelmente indefinido


Eu quero criar uma classe em TypeScript, que implemente uma interface de uma maneira que seja necessário ter todas as propriedades dessa interface, mesmo as opcionais, mas permita que as propriedades opcionais sejam undefined.

Este é o tipo Required_ish<T> que seria mais amplo do que Required<T>, mas mais restrito do que T, porque exigiria que eu listasse explicitamente todas as propriedades. No entanto, os mesmos valores seriam atribuíveis aos parâmetros de Required_ish<T> e T.

Eu tentei isso, mas parece fazer a mesma coisa que Required:

type Required_ish<T> = T & { [K in keyof T]-?: T[K] | undefined }

As propriedades desejadas:

interface Foo {
    a: string;
    b?: number;
}

class Bar1 implements Foo               { a = ''; } // allowed
class Bar2 implements Required_ish<Foo> { a = ''; } // error
class Bar3 implements Required<Foo>     { a = ''; } // error

class Bar4 implements Foo               { a = ''; b = undefined; } // allowed
class Bar5 implements Required_ish<Foo> { a = ''; b = undefined; } // allowed
class Bar6 implements Required<Foo>     { a = ''; b = undefined; } // error

class Bar7 implements Foo               { a = ''; b = 0; } // allowed
class Bar8 implements Required_ish<Foo> { a = ''; b = 0; } // allowed
class Bar9 implements Required<Foo>     { a = ''; b = 0; } // allowed

Resposta #1

Finalmente encontrei uma solução. E é mais legível do que eu esperava que fosse.

type Required_ish<T> =
{
    [K in keyof Required<T>]: T[K]
};

Ok, mas por que isso funciona? Vamos analisar isso.

Primeiro, eu começo com um tipo mapeado homomórfico:

type Required_ish<T> = { [K in keyof T]: T[K] }

Tipos mapeados são um recurso que permite modificar um tipo, mas manter sua estrutura interna (uma tupla continuará sendo uma tupla) e modificadores de propriedades (readonly, opcional) a menos que você seja explícito sobre isso. A linha que acabei de escrever é mais ou menos um tipo identidade: para cada chave de T ele retorna o mesmo tipo e modificadores que T tinha.

Agora há dois modificadores que permitem alterar a obrigatoriedade de uma propriedade:

{ [K in keyof T]?:  T[K] } // add the optional modifier
{ [K in keyof T]-?: T[K] } // remove the optional modifier

Pode ser tentador usar -? aqui, mas infelizmente este modificador faz duas coisas. Não apenas remove o modificador opcional da propriedade, ele também remove undefined do tipo resultante. Isso significa que sempre que você usa -?, você não tem como retornar um tipo que estenda undefined.

Felizmente, podemos obter as chaves não de T, mas de Required<T>. Como o compilador pega todos os modificadores do lado esquerdo (a parte antes de :), a propriedade terá os mesmos modificadores que Required<T>[K]. Isso significa que ela nunca será opcional e o modificador readonly permanecerá inalterado. E como não usamos o modificador -?, T[K] permanecerá intacto e possivelmente indefinido.

Dessa forma, contornamos efetivamente o efeito colateral de -? e o tipo resultante faz exatamente o que eu pedi.

Artigos relacionados