TypeScript: Migração de um projeto JavaScript com…
Última atualização: 2026-08-26
A migração de um projeto JavaScript existente para o TypeScript é o cenário mais comum e prático — esta lição explica como realizar a migração de forma segura e gradual.
1. Visão geral das estratégias de migração
(1) Três estratégias de migração
| Estratégia | Velocidade | Risco | Cenários adequados |
|---|---|---|---|
| Migração completa única | Rápida | Alta | Projetos pequenos (< 20 arquivos) |
| Migração incremental, arquivo por arquivo | Média | Baixa | Projetos de médio a grande porte (recomendado) |
| Migração gradual do JSDoc | Lenta | Muito baixa | Projetos grandes/críticos |
(2) Visão geral das etapas da migração
1. Initialization tsconfig.json(Relaxed Mode)
2. Install @types packages
3. Open allowJs——TS and JS Coexistence
4. File by file .js → .ts(Starting with the leaf file)
5. Gradually Enable Strict Options
6. Finally Unlocked strict
2. Etapa 1: Inicializar a configuração
(1) Gerar o arquivo tsconfig.json
tsc --init
(2) Configuração inicial da chave — Modo flexível
{
"compilerOptions": {
"target": "ES2020",
"module": "ESNext",
"moduleResolution": "node",
"allowJs": true,
"checkJs": false,
"noImplicitAny": false,
"strict": false,
"outDir": "./dist",
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*"],
"exclude": ["node_modules", "dist"]
}
strict ou noImplicitAny no início da migração — primeiro certifique-se de que o projeto seja compilado e, em seguida, torne as restrições mais rigorosas gradualmente.
(3) Declaração do tipo de instalação
# Install the type declarations required by the project
npm install @types/node @types/express @types/lodash --save-dev
3. Etapa 2: use allowJs para habilitar a coexistência de JS e TS
(1) Ativar allowJs
{
"compilerOptions": {
"allowJs": true
}
}
O allowJs permite que o compilador do TypeScript aceite arquivos .js — os projetos podem conter arquivos JS e TS sem que um afete o outro.
(2) Compatibilidade de importação
// main.ts —— Can be imported .js Documents
import { helper } from "./utils"; // utils.js Just being there is enough
// utils.js —— JS Documents,Untyped Annotations
function helper(value) {
return value.toString();
}
(3) checkJs — Verificação opcional de tipos em JavaScript
{
"compilerOptions": {
"checkJs": true
}
}
Quando o checkJs estiver ativado, o TypeScript também verificará se há erros de tipo nos arquivos .js (com base nos comentários JSDoc e na inferência de tipos). Recomenda-se mantê-lo desativado durante a fase inicial de migração — ative-o somente depois que os arquivos JS tiverem sido gradualmente convertidos para TS.
4. Etapa 3: Migrar arquivo por arquivo
(1) Sequência de migração
Comece com o arquivo que tem o menor número de dependências — o arquivo “folha” (que contém funções utilitárias, constantes etc., sem importar arquivos de outros projetos):
Recommended Migration Order:
1. Constants File(config.js → config.ts)
2. Utility Functions(utils.js → utils.ts)
3. Type Definitions(types.js → types.ts)
4. Data Model(models.js → models.ts)
5. Service Layer(services.js → services.ts)
6. Controller/Routing(controllers.js → controllers.ts)
7. Input File(index.js → index.ts)
(2) Etapas para a migração de um único arquivo
// ── Before the Migration:utils.js ──
function formatPrice(price, currency) {
return currency + price.toFixed(2);
}
function clamp(value, min, max) {
return Math.min(Math.max(value, min), max);
}
// ── After the migration:utils.ts ──
function formatPrice(price: number, currency: string = "¥"): string {
return currency + price.toFixed(2);
}
function clamp(value: number, min: number, max: number): number {
return Math.min(Math.max(value, min), max);
}
(3) Técnicas de migração — Comece com any e, em seguida, refine
// Step 1:Add a type annotation,Uncertain usage any
function process(data: any, options: any): any {
return data.filter(item => item.active);
}
// Step 2:Gradual Replacement any For a specific type
interface DataItem {
id: number;
name: string;
active: boolean;
}
interface Options {
limit?: number;
sort?: "asc" | "desc";
}
function process(data: DataItem[], options: Options = {}): DataItem[] {
let result = data.filter(item => item.active);
if (options.sort === "desc") result.reverse();
if (options.limit) result = result.slice(0, options.limit);
return result;
}
▶ Exemplo: Migração de um arquivo de rota do Express
// ── Before the Migration:users.js ──
const express = require("express");
const router = express.Router();
router.get("/", async (req, res) => {
const users = await User.findAll();
res.json(users);
});
router.post("/", async (req, res) => {
const { name, email } = req.body;
const user = await User.create({ name, email });
res.status(201).json(user);
});
module.exports = router;
Saída:
// API endpoint response (status 200)
// Returns JSON data
// ── After the migration:users.ts ──
import { Router, Request, Response } from "express";
const router = Router();
interface CreateUserBody {
name: string;
email: string;
}
router.get("/", async (_req: Request, res: Response) => {
const users = await User.findAll();
res.json(users);
});
router.post("/", async (req: Request<{}, {}, CreateUserBody>, res: Response) => {
const { name, email } = req.body;
const user = await User.create({ name, email });
res.status(201).json(user);
});
export default router;
5. Anotações de tipo do JSDoc
Se você não quiser alterar a extensão do arquivo, pode usar o JSDoc para adicionar um tipo ao seu arquivo JS:
(1) Anotações de tipo básicas
// utils.js — Use JSDoc to add types
/**
* Pricing Format
* @param {number} price - Price
* @param {string} [currency="¥"] - Currency Symbol
* @returns {string} Formatted Price
*/
function formatPrice(price, currency = "¥") {
return currency + price.toFixed(2);
}
/**
* @typedef {Object} User
* @property {number} id
* @property {string} name
* @property {string} email
*/
/**
* Search for a User
* @param {number} id
* @returns {Promise<User>}
*/
async function findUser(id) {
// ...
}
module.exports = { formatPrice, findUser };
(2) Referenciando tipos JSDoc em arquivos TS
// main.ts
import { findUser } from "./utils"; // utils.js has JSDoc types
let user = await findUser(1); // ✅ Type inference is correct(From JSDoc)
(3) Tags de tipo comuns do JSDoc
| Tag | Finalidade | Exemplo |
|---|---|---|
@type |
Tipo de variável | @type {string} |
@param |
Tipo de parâmetro | @param {number} x |
@returns |
Tipo de retorno | @returns {string} |
@typedef |
Definir tipo | @typedef {Object} User |
@property |
Propriedades do objeto | @property {string} name |
@template |
Parâmetros genéricos | @template T |
@callback |
Tipo de retorno de chamada | @callback Handler |
6. Armadilhas comuns na migração
(1) Armadilha 1: Explosão implícita any
// A large number of implicit variables after migration any——Don't rush to start it noImplicitAny
function process(data) { // data Implicit any
return data.map(item => item.name); // item Me too any
}
Solução: Primeiro, faça com que o projeto seja compilado com sucesso; depois, adicione as anotações de tipo uma a uma; e, por fim, desative noImplicitAny.
(2) Armadilha 2: Conflito entre module.exports e import
// JS For use with documents module.exports
module.exports = function() { /* ... */ };
// TS Import Requirements esModuleInterop
import fn from "./legacy"; // Required esModuleInterop: true
(3) Armadilha 3: Bibliotecas de terceiros sem tipagem
import untypedLib from "untyped-lib"; // ❌ Cannot find the declaration file
// Temporary solution——Create shim.d.ts
declare module "untyped-lib" {
const lib: any;
export default lib;
}
(4) Armadilha 4: Perda do tipo this
// JS in this Dynamic Binding
const obj = {
name: "Charlie",
greet() {
console.log(this.name); // ✅ JS in OK
}
};
// TS in this Needs annotation
const obj2 = {
name: "Charlie",
greet(this: { name: string }) {
console.log(this.name); // ✅ TS Needed in this Parameters
}
};
▶ Exemplo: Adicionando tipos a objetos JavaScript simples
// ── Before: shapes.js ──
const shapes = [
{ type: "circle", radius: 5 },
{ type: "rectangle", width: 10, height: 20 },
{ type: "circle", radius: 3 }
];
function totalArea(shapes) {
return shapes.reduce((sum, s) => {
if (s.type === "circle") return sum + Math.PI * s.radius * s.radius;
return sum + s.width * s.height;
}, 0);
}
Saída:
// Executed successfully
// ── After: shapes.ts ──
interface Circle { type: "circle"; radius: number; }
interface Rectangle { type: "rectangle"; width: number; height: number; }
type Shape = Circle | Rectangle;
const shapes: Shape[] = [
{ type: "circle", radius: 5 },
{ type: "rectangle", width: 10, height: 20 },
{ type: "circle", radius: 3 }
];
function totalArea(shapes: Shape[]): number {
return shapes.reduce((sum, s) => {
if (s.type === "circle") return sum + Math.PI * s.radius * s.radius;
return sum + s.width * s.height;
}, 0);
}
▶ Exemplo: Migração de JSDoc para TypeScript
// ── Before: math.js (JSDoc-typed) ──
/**
* @template T
* @param {T[]} arr
* @param {(item: T) => boolean} predicate
* @returns {T[]}
*/
function filter(arr, predicate) {
return arr.filter(predicate);
}
/**
* @typedef {Object} Point
* @property {number} x
* @property {number} y
*/
/**
* @param {Point} a
* @param {Point} b
* @returns {number}
*/
function distance(a, b) {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
Saída:
// Executed successfully
// ── After: math.ts (native TS types) ──
function filter<T>(arr: T[], predicate: (item: T) => boolean): T[] {
return arr.filter(predicate);
}
interface Point {
x: number;
y: number;
}
function distance(a: Point, b: Point): number {
return Math.sqrt((a.x - b.x) ** 2 + (a.y - b.y) ** 2);
}
❓ Perguntas Frequentes
P: Quanto tempo leva para migrar um projeto grande? R: Depende da quantidade de código e da equipe. Para projetos de pequeno a médio porte (10.000 linhas ou menos), leva cerca de 1 a 2 semanas. Projetos grandes (100.000 linhas ou mais) podem exigir de 1 a 3 meses de migração incremental. O segredo é não fazer todas as alterações de uma só vez — migre um arquivo por vez, garantindo que o código seja compilado com sucesso após cada etapa.
P: O que é melhor: as anotações de tipo do JSDoc ou as do TypeScript? R: As anotações de tipo do TypeScript são melhores — elas oferecem uma sintaxe mais concisa, são mais poderosas e contam com melhor suporte dos editores. O JSDoc é uma solução intermediária que não exige a alteração da extensão do arquivo, tornando-o adequado para situações em que não é possível modificar o nome do arquivo. O objetivo final continua sendo converter os arquivos JS para TypeScript.
P: E quanto à CI durante a migração? R: Adicione
tsc --noEmità CI para verificação de tipos (sem gerar arquivos). Nas fases iniciais da migração,--noImplicitAny falseé permitido para garantir que a CI não falhe devido a problemas de tipos. À medida que o processo se torna gradualmente mais rigoroso, a verificação de tipos na CI se torna cada vez mais rigorosa.
P: Qual devo usar,
ts-ignoreouts-expect-error? R: Use@ts-expect-errorprimeiro — ele gerará um erro se não houver nenhum erro de tipo na linha seguinte (para evitar que você se esqueça de remover o comentário após a correção). O@ts-ignoreignora erros incondicionalmente, o que pode ocultar erros que já foram corrigidos. Ambos são soluções provisórias; no final das contas, você deve corrigir os problemas de tipo.
📖 Resumo
- Estratégia de migração: migração incremental, arquivo por arquivo (recomendada), começando pelos arquivos folha e terminando pelos arquivos de entrada
- Configuração inicial no modo tolerante: allowJs ativado para permitir a coexistência, noImplicitAny desativado, strict desativado
- Migração em uma única etapa: .js → .ts — primeiro adicione anotações de tipo (usando
anycomo alternativa), depois refine os tipos - As anotações de tipo JSDoc são uma solução intermediária que não altera as extensões dos arquivos — ideal para uma migração gradual em projetos de grande porte
- Armadilhas comuns: explosão implícita de
any, conflitos demodule.exports, bibliotecas de terceiros sem tipos e tipos ausentes dethis - Ative as opções rigorosas gradualmente — cada vez que uma opção for ativada, todos os erros serão corrigidos
📝 Exercícios
- Exercício básico (Dificuldade ⭐): Migre um arquivo utilitário simples em JavaScript (3 a 5 funções) para TypeScript — adicione anotações de tipo para os parâmetros e valores de retorno de cada função e certifique-se de que ele seja compilado com sucesso.
- Exercício avançado (Dificuldade: ⭐⭐): Configure
tsconfig.jsonpara oferecer suporte a projetos mistos de JS/TS — habiliteallowJs, use o JSDoc para adicionar tipos a um arquivo JS e, em seguida, importe-o e utilize-o em um arquivo TS. - Desafio (Dificuldade: ⭐⭐⭐): Simule a migração de um projeto Express — configure
tsconfig, adicione tipos às Request/Response do Express, lide com a segurança de tipos parareq.body(defina a interfacebody) e lide com os tipos parareq.params.