Menu

Construtor em TypeScript: parameter properties e overloads

Como tipar o construtor de uma classe em TypeScript: parâmetros tipados e opcionais, parameter properties como constructor(private name: string), ordem de inicialização dos campos, overloads de construtor, chamadas a super em subclasses, construtores privados e tipos de construtor com new.

Esta página tem editores executáveis - edite, execute e veja a saída na hora.

Um construtor no TypeScript é o método constructor da classe com parâmetros tipados. Ele não tem anotação de tipo de retorno: sempre produz uma instância da classe.

Escrever constructor(): Product gera o erro TS1093 (Type annotation cannot appear on a constructor declaration). Todo o resto sobre como o new funciona é JavaScript puro.

Parameter properties

O padrão acima (declarar um campo, receber um parâmetro, copiá-lo) é tão comum que o TypeScript tem uma abreviação. Coloque public, private, protected ou readonly antes de um parâmetro do construtor e ele vira um campo:

Esse é um dos poucos recursos do TypeScript que geram código. O compilador escreve as atribuições para você:

class Product {
    name;
    price;
    sku;
    constructor(name, price, sku) {
        this.name = name;
        this.price = price;
        this.sku = sku;
    }
    // ...
}

Como não se trata só de tipos apagados, as parameter properties não rodam com o type stripping nativo do Node (node file.ts falha com ERR_UNSUPPORTED_TYPESCRIPT_SYNTAX), e a opção do compilador erasableSyntaxOnly as aponta como erro TS1294. Projetos que rodam arquivos .ts diretamente escrevem os campos à mão.

Parâmetros opcionais e com valor padrão

Os parâmetros do construtor seguem as mesmas regras dos parâmetros de função: ? torna um opcional, um valor padrão o torna opcional e o preenche, e os obrigatórios vêm primeiro.

Parameter properties também podem ter padrões. O tipo de color dentro da classe é string | undefined, então o código que o usa precisa verificar undefined antes.

Para construtores com muitas opções, um único objeto de opções fica mais legível que uma lista longa de parâmetros posicionais: constructor(opts: { prefix: string; level?: "info" | "debug" }).

Ordem de inicialização dos campos

Os inicializadores de campo rodam antes do corpo do construtor, e as parameter properties são atribuídas no começo do corpo do construtor. Então um inicializador de campo não pode ler uma parameter property. O TypeScript pega isso:

index.ts(3,18): error TS2729: Property 'size' is used before its initialization.
index.ts(3,30): error TS2729: Property 'size' is used before its initialization.

Em tempo de execução o inicializador veria undefined e produziria NaN. Calcule o valor no corpo do construtor:

class Grid {
    cells: number;

    constructor(public size: number) {
        this.cells = size * size;
    }
}

console.log(new Grid(3).cells); // 9

Overloads de construtor

Uma classe tem exatamente uma implementação de construtor, mas você pode listar várias overload signatures acima dela. Quem chama só vê os overloads; a implementation signature precisa ser compatível com todos eles.

Overloads ficam difíceis de ler a partir de dois ou três formatos. Métodos estáticos de fábrica com nomes descritivos (Color.fromHex("#ff8800"), Color.fromRgb(10, 20, 30)) costumam ser mais claros e não precisam de narrowing dentro de um único corpo.

Chamando super em uma subclasse

Uma classe derivada que declara o próprio construtor precisa chamar super(...) com os argumentos do pai, e precisa fazer isso antes de usar o this. Esquecer a chamada gera o erro TS2377 (Constructors for derived classes must contain a 'super' call), e usar o this antes gera o TS17009 ('super' must be called before accessing 'this' in the constructor of a derived class). As duas regras espelham regras de tempo de execução do JavaScript.

class Animal {
    constructor(public name: string) {}
}

class Dog extends Animal {
    tricks: string[];

    constructor(name: string, ...tricks: string[]) {
        super(name); // must come first
        this.tricks = tricks;
    }
}

const rex = new Dog("Rex", "sit", "roll");
console.log(rex); // Dog { name: 'Rex', tricks: [ 'sit', 'roll' ] }

Uma subclasse que não declara construtor herda o do pai, com os mesmos tipos de parâmetro. Mais sobre subclasses em herança.

Construtores privados e configuração assíncrona

Um construtor private só pode ser chamado de dentro da classe. Fora dela, new gera o erro TS2673. É assim que você obriga quem chama a passar por uma fábrica, para um singleton ou para uma configuração que precisa ser assíncrona (construtores não podem ser async).

Aqui o private é só uma regra de tempo de compilação. O JavaScript gerado tem um construtor comum, então código JavaScript puro ainda conseguiria chamar new Connection(...).

Tipos de construtor

Para aceitar uma classe como valor, tipe o parâmetro com uma construct signature: new (...args) => Instance. typeof MyClass também funciona, mas amarra o parâmetro à assinatura de construtor e aos membros estáticos daquela classe específica.

ConstructorParameters e InstanceType extraem a tupla de parâmetros e o tipo da instância de um tipo de construtor; eles são tratados junto com os outros helpers de função em ReturnType e Parameters.

Perguntas frequentes

O que são parameter properties no TypeScript?

Um parâmetro de construtor com um modificador (public, private, protected ou readonly) declara um campo e o atribui em um passo só. constructor(private name: string) {} é uma abreviação de declarar private name: string e escrever this.name = name. O compilador gera essa atribuição na saída.

Uma classe TypeScript pode ter vários construtores?

Não várias implementações. Uma classe tem um único construtor, mas você pode escrever várias overload signatures acima dele para que quem chama veja listas de parâmetros distintas. Métodos estáticos de fábrica como Color.fromHex() e Color.fromRgb() costumam ser mais claros que overloads.

Um construtor pode ser async no TypeScript?

Não. Um construtor sempre retorna a nova instância, nunca uma promise, e async constructor() é rejeitado com o erro TS1089 ('async' modifier cannot appear on a constructor declaration). Use um construtor privado mais um método estático async create() que faz o trabalho assíncrono e depois chama new.

Como tipar o construtor de uma classe como parâmetro?

Use uma construct signature: new (name: string) => User, ou typeof User para aquela classe exata. Uma fábrica genérica fica assim: function make<T>(ctor: new () => T): T { return new ctor(); }. ConstructorParameters<typeof User> dá a lista de parâmetros como tupla.

Por que recebo "'super' must be called before accessing 'this'"?

Esse é o erro TS17009. Em uma classe que faz extends de outra, o construtor pai cria o objeto, então o this não existe até super(...) ter rodado. Mova a chamada a super para antes de qualquer linha que use o this.

Coddy programming languages illustration

Aprenda a programar com o Coddy

COMEÇAR