Um tipo de função descreve os parâmetros e o valor de retorno de uma função, escrito com uma seta: (a: number, b: number) => number. Dê um nome a ele com type e você pode usá-lo em variáveis, parâmetros e propriedades de objeto.
A função atribuída a add não precisa de anotações: os tipos dos parâmetros vêm de Operation. Isso se chama contextual typing, e é o principal motivo para dar nome a tipos de função.
Sintaxe de tipos de função
O formato é (parameters) => ReturnType. Cada parâmetro precisa de um nome e de um tipo. Os nomes estão ali para a leitura e para as dicas do editor; qualquer função com tipos de parâmetro compatíveis corresponde, seja qual for o nome que ela dá a eles.
type Predicate = (value: number) => boolean;
type Formatter = (value: number, digits?: number) => string; // optional parameter
type Logger = (...parts: string[]) => void; // rest parameter
type Factory = () => { id: number }; // no parameters
O nome não é opcional. (string) => void declara um parâmetro chamado string sem tipo, e o strict mode o rejeita com TS7051: Parameter has a name but no type. Did you mean 'arg0: string'?. Escreva (value: string) => void.
Tipando arrow functions
Uma arrow function pode ser tipada de dois jeitos. Anote a própria função, ou anote a variável com um tipo de função e deixe os parâmetros serem inferidos.
A forma 1 é a escolha usual para uma função avulsa. A forma 3 compensa quando várias funções compartilham uma assinatura (handlers, comparadores, conversores), porque a assinatura é escrita uma vez e uma mudança nela é verificada em todos os lugares.
Tipos de callback
Um parâmetro cujo tipo é um tipo de função é um callback. A função de quem chama é verificada contra ele, e os parâmetros dela são inferidos a partir dele.
Duas regras deixam os callbacks agradáveis de usar:
- Menos parâmetros não tem problema. Uma função que recebe um parâmetro pode ser passada onde dois são oferecidos. O JavaScript ignora argumentos extras, então o TypeScript permite. Mais parâmetros do que o tipo oferece é erro (
Target signature provides too few arguments.). - Um retorno
voidaceita qualquer coisa. Um callback tipado como(...) => voidpode retornar um valor; quem chama promete não usá-lo. É por isso quelist.forEach((x) => other.push(x))compila, mesmo quepushretorne um número.
Os tipos dos parâmetros ainda precisam ser compatíveis. Passar (x: string) => ... onde (a: number, b: number) => number é esperado falha com Types of parameters 'x' and 'a' are incompatible.
Call signatures
(n: number) => string é uma abreviação de um tipo de objeto com uma call signature: { (n: number): string }. Você precisa da forma longa quando a função também carrega propriedades.
O TypeScript permite acrescentar propriedades a uma função declarada com const no mesmo escopo, e as acompanha no tipo da função. Uma interface também pode ter uma call signature: interface Counter { (): number; count: number }.
Uma construct signature descreve algo chamado com new. Ela acrescenta new na frente: new (name: string) => User, ou { new (name: string): User } na forma de objeto. É assim que você tipa um parâmetro que recebe uma classe.
Sintaxe de método vs sintaxe de propriedade
Em um tipo de objeto, um membro função pode ser escrito como método, handle(value: string): void, ou como uma propriedade que guarda uma função, handle: (value: string) => void. Parecem intercambiáveis, mas são verificados de forma diferente com strict:
O compilador mostra:
index.ts(8,27): error TS2322: Type '(value: string) => void' is not assignable to type '(value: string | number) => void'.
Types of parameters 'value' and 'value' are incompatible.
Type 'string | number' is not assignable to type 'string'.
Type 'number' is not assignable to type 'string'.
O erro está certo: onlyStrings não consegue lidar com um número. A forma de método deixa o mesmo erro passar (os parâmetros de métodos são verificados de forma "bivariante" por razões históricas), e a.handle(42) quebraria em tempo de execução com TypeError: value.toUpperCase is not a function. Apague a linha do b para ver essa quebra. Quando você escreve seus próprios tipos de objeto, a forma de propriedade pega mais erros.
Evite o tipo Function
Function é o tipo nativo que todo valor função satisfaz. Quase nunca é o que você quer: o TypeScript não conhece os parâmetros nem o tipo de retorno, então toda chamada é aceita e retorna any.
Troque Function pela assinatura real. Quando você realmente aceita qualquer função (um debounce genérico, um wrapper de log), (...args: never[]) => unknown aceita toda função e ainda mantém o resultado como unknown, e não any. Para funções cujos tipos dependem da entrada, veja generics: um tipo de função genérico fica assim: <T>(value: T) => T.
Perguntas frequentes
Como definir um tipo de função no TypeScript?
Use a sintaxe de arrow: (a: number, b: number) => number. Dê um nome a ele com um type alias, type Compare = (a: number, b: number) => number;, e use esse nome em variáveis, parâmetros e propriedades. Os nomes dos parâmetros fazem parte da sintaxe, mas servem só como documentação: uma função com nomes de parâmetro diferentes continua correspondendo.
Como tipar um parâmetro de callback no TypeScript?
Escreva o tipo de função como tipo do parâmetro: function onEach(items: string[], cb: (item: string, index: number) => void) { ... }. Quem chama pode passar uma função que recebe menos parâmetros, e os parâmetros do próprio callback são inferidos a partir desse tipo, então onEach(list, (item) => ...) não precisa de anotação.
Por que não usar o tipo Function no TypeScript?
Function aceita qualquer função, e chamá-la não é verificado: quaisquer argumentos são aceitos e o resultado é any. Escreva a assinatura real, como () => void ou (value: string) => number, ou (...args: never[]) => unknown quando você realmente aceita qualquer função.
Qual é a diferença entre um tipo de função e uma call signature?
Eles descrevem a mesma coisa. (n: number) => string é uma abreviação do tipo de objeto { (n: number): string }. A forma de call signature é necessária quando a função também tem propriedades, por exemplo { (n: number): string; label: string }.
Como tipar uma arrow function no TypeScript?
Ou anote os parâmetros e o tipo de retorno inline, const half = (n: number): number => n / 2;, ou dê à variável um tipo de função e deixe os parâmetros serem inferidos: const half: (n: number) => number = (n) => n / 2;.