Recentemente comecei minhas aventuras em Typescript e percebi algo importante: é preciso ser muito honesto com a declaração de tipos.
O problema com a tipagem do Typescript é que ela só pode ser verificada em tempo de transpilação, uma vez que a transpilação gera código Javascript, cuja tipagem é dinâmica e também bastante fraca.
Vamos a um exemplo:
import * as sequelize from "sequelize"
export interface Employee {
id: number
name: string
birth: Date
}
export function registerEmployee(employee: Employee): Promise<boolean> {
return sequelize.query(
"INSERT INTO t_employee VALUES (?, ?, ?)",
{
replacements: [ employee.id, employee.name, employee.birth ],
type: sequelize.QueryType.INSERT,
}
).then(() => true).catch(err => {
console.error(err)
return false
})
}
Parece um código bastante seguro, uma vez que, se for passado um elemento que não seja um Employee
para registerEmployee
, o código nem
mesmo transpila.
Porém esta é uma falsa segurança. Considere dois casos:
employee
de uma resposta ou uma requisição do Express.Nesses dois casos, não há como garantir que employee
será do tipo Employee
.
Para solucionar este problema precisamos ser bastante honestos – e verborrágicos – quanto à representação dos tipos.
Neste caso, o objeto recebido pode não ter qualquer um dos atributos esperados – ou pior: nem ser um object
.
Temos então de lidar com as possibilidades – usaremos Underscore.js para ajudar:
import * as _ from "underscore"
import * as sequelize from "sequelize"
export interface Employee {
id: number
name: string
birth: Date
}
function castEmployee(data: any): Employee {
const check = _.isObject(data)
&& _(data).has("id") && _.isNumber(data.id)
&& _(data).has("name") && _.isString(data.name)
&& _(data).has("birth") && _.isDate(birth)
if (check)
return <Employee>data
throw new TypeError(`${data} is not an Employee`)
}
export function registerEmployee(employee: Employee): Promise<boolean> {
employee = castEmployee(employee)
return sequelize.query(
"INSERT INTO t_employee VALUES (?, ?, ?)",
{
replacements: [ employee.id, employee.name, employee.birth ],
type: sequelize.QueryType.INSERT,
}
).then(() => true).catch(err => {
console.error(err)
return false
})
}
Parece rude levantar uma exceção, mas é honesto – tipo errado leva a um TypeError
.
Em resumo: se é possível que um valor seja any
, é preciso que ele seja tratado como tal, e o tratamento e casting precisam ser feitos
manualmente. Se um valor puder ser undefined
, é preciso tratá-lo
como tal (Employee | undefined
, por exemplo). Não podemos confiar
totalmente na tipagem estática se o código será compilado para uma
tecnologia que não suporta tal ferramenta.