Миграция с JavaScript
TypeScript не существует в вакууме. Он был создан с учетом экосистемы JavaScript, и сегодня много JavaScript кода существует. Перевод JavaScript кодовой базы на TypeScript, хотя и несколько утомителен, обычно не сложен. В этом учебнике мы рассмотрим, как вы можете начать. Мы предполагаем, что вы достаточно хорошо ознакомились с руководством, чтобы писать новый код на TypeScript.
Если вы хотите конвертировать проект React, мы рекомендуем сначала ознакомиться с Руководством по преобразованию React.
Настройка каталогов
Если вы работаете с обычным JavaScript, скорее всего, вы запускаете свой JavaScript напрямую, где ваши .js файлы находятся в каталоге src, lib, или dist, а затем запускаете его как нужно.
В таком случае файлы, которые вы написали, будут использованы в качестве входных данных для TypeScript, и вы будете запускать созданные им выходные данные. Во время миграции JS в TS нам нужно разделить входные файлы, чтобы предотвратить перезапись TypeScript. Если выходные файлы должны находиться в определенном каталоге, то это будет ваш выходной каталог.
Вы также можете выполнять некоторые промежуточные шаги над JavaScript, такие как сборка или использование другого транспайлера, такого как Babel. В этом случае у вас может быть уже настроена такая структура папок.
Отныне мы будем предполагать, что структура вашего каталога выглядит примерно так:
projectRoot ├── src │ ├── file1.js │ └── file2.js ├── built └── tsconfig.json
Если у вас есть папка tests вне каталога src, у вас может быть один tsconfig.json в src, и один в tests тоже.
Написание файла конфигурации
TypeScript использует файл, называемый tsconfig.json для управления параметрами проекта, такими как включение файлов и типы проверок. Давайте создадим простейший для нашего проекта:
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "es5"
},
"include": ["./src/**/*"]
} Здесь мы указываем несколько вещей TypeScript:
- Прочитать все понятные ему файлы в каталоге
src(сinclude). - Принимать JavaScript файлы в качестве входных данных (с
allowJs). - Выводить все выходные файлы в
built(сoutDir). - Преобразовать более новые конструкции JavaScript в более старую версию, например, ECMAScript 5 (используя
target).
В этот момент, если вы попытаетесь запустить tsc в корне вашего проекта, вы увидите выходные файлы в каталоге built. Структура файлов в built должна быть идентична структуре src. Теперь у вас работает TypeScript с вашим проектом.
Первые преимущества
Даже на этом этапе вы можете получить значительные преимущества от того, что TypeScript понимает ваш проект. Если вы откроете редактор, такой как VS Code или Visual Studio, вы увидите, что часто можете получить поддержку инструментов, таких как автодополнение. Вы также можете обнаруживать определенные ошибки с помощью таких опций, как:
-
noImplicitReturns, которая предотвращает забывание return в конце функции. -
noFallthroughCasesInSwitch, которая полезна, если вы никогда не захотите забыть операторbreakмеждуcaseв блокеswitch.
TypeScript также будет предупреждать об недостижимом коде и метках, которые можно отключить с помощью allowUnreachableCode и allowUnusedLabels соответственно.
Интеграция со средствами сборки
В вашей конвейерной сборке могут быть дополнительные этапы. Возможно, вы конкатенируете что-то с каждым из ваших файлов. Каждый инструмент сборки отличается, но мы постараемся охватить суть.
Gulp
Если вы используете Gulp, у нас есть учебник о использовании Gulp с TypeScript и интеграции с распространенными средствами сборки, такими как Browserify, Babelify и Uglify. Подробнее можно прочитать там.
Webpack
Интеграция с Webpack довольно проста. Вы можете использовать ts-loader, загрузчик TypeScript, в сочетании с source-map-loader для более удобной отладки. Просто выполните
npm install ts-loader source-map-loader
и объедините параметры из следующего в файл webpack.config.js:
module.exports = {
entry: "./src/index.ts",
output: {
filename: "./dist/bundle.js",
},
// Enable sourcemaps for debugging webpack's output.
devtool: "source-map",
resolve: {
// Add '.ts' and '.tsx' as resolvable extensions.
extensions: ["", ".webpack.js", ".web.js", ".ts", ".tsx", ".js"],
},
module: {
rules: [
// All files with a '.ts' or '.tsx' extension will be handled by 'ts-loader'.
{ test: /\.tsx?$/, loader: "ts-loader" },
// All output '.js' files will have any sourcemaps re-processed by 'source-map-loader'.
{ test: /\.js$/, loader: "source-map-loader" },
],
},
// Other options...
}; Важно отметить, что ts-loader должен работать перед любым другим загрузчиком, который обрабатывает файлы .js.
Вы можете увидеть пример использования Webpack в нашем учебнике о React и Webpack.
Переход к файлам TypeScript
На этом этапе вы, вероятно, готовы начать использовать файлы TypeScript. Первый шаг — переименовать один из ваших файлов .js в .ts. Если ваш файл использует JSX, вам нужно переименовать его в .tsx.
Закончили? Отлично! Вы успешно мигрировали файл с JavaScript на TypeScript!
Конечно, это может показаться не очень удобным. Если вы откроете этот файл в редакторе с поддержкой TypeScript (или запустите tsc --pretty), вы можете увидеть красные волнистые линии на определенных строках. Вы должны рассматривать их так же, как красные волнистые линии в редакторе, таком как Microsoft Word. TypeScript все равно будет компилировать ваш код, так же как Word все равно позволит вам печатать документы.
Если это кажется вам слишком слабым, вы можете ужесточить это поведение. Например, если вы не хотите, чтобы TypeScript компилировался в JavaScript при возникновении ошибок, вы можете использовать опцию noEmitOnError. В этом смысле у TypeScript есть регулятор строгости, и вы можете настроить его так, как вам нужно.
Если вы планируете использовать более строгие параметры, лучше включить их сейчас (см. Усиление проверок ниже). Например, если вы никогда не хотите, чтобы TypeScript неявно определял any для типа без явного указания, вы можете использовать noImplicitAny перед началом модификации файлов. Хотя это может показаться несколько сложным, долгосрочные преимущества становятся очевидными гораздо быстрее.
Удаление ошибок
Как мы уже упоминали, после конвертации вы не исключены от получения сообщений об ошибках. Важно пройтись по ним по одному и решить, как с ними бороться. Часто это будут реальные ошибки, но иногда вам нужно будет немного лучше объяснить, что вы пытаетесь сделать TypeScript.
Импорт из модулей
Вы можете начать получать множество ошибок, таких как Cannot find name 'require'., и Cannot find name 'define'.. В этих случаях, вероятно, вы используете модули. Хотя вы можете просто убедить TypeScript, что они существуют, написав
// For Node/CommonJS declare function require(path: string): any;
или
// For RequireJS/AMD declare function define(...args: any[]): any;
лучше избавиться от этих вызовов и использовать синтаксис TypeScript для импортов.
Сначала вам нужно включить систему модулей, задав опцию TypeScript module. Допустимые опции — commonjs, amd, system, и umd.
Если у вас был следующий код Node/CommonJS:
var foo = require("foo");
foo.doStuff(); или следующий код RequireJS/AMD:
define(["foo"], function (foo) {
foo.doStuff();
}); то вы написали бы следующий код TypeScript:
import foo = require("foo");
foo.doStuff(); Получение файлов объявлений
Если вы начали переходить на импорты TypeScript, вы, вероятно, столкнетесь с ошибками, такими как Cannot find module 'foo'.. Проблема здесь в том, что у вас, вероятно, нет файлов объявлений для описания вашей библиотеки. К счастью, это довольно просто. Если TypeScript жалуется на пакет, например, lodash, вы можете просто написать
npm install -S @types/lodash
Если вы используете опцию модуля, отличную от commonjs, вам нужно установить опцию moduleResolution в значение node.
После этого вы сможете импортировать lodash без проблем и получить точные автодополнения.
Экспорт из модулей
Обычно экспорт из модуля подразумевает добавление свойств к значению, например, exports или module.exports. TypeScript позволяет использовать операторы экспорта верхнего уровня. Например, если вы экспортировали функцию следующим образом:
module.exports.feedPets = function (pets) {
// ...
}; вы могли бы записать это как следующее:
export function feedPets(pets) {
// ...
} Иногда вы полностью перезаписываете объект экспорта. Это распространенный метод, который люди используют, чтобы сделать свои модули сразу вызываемыми, как в этом фрагменте:
var express = require("express");
var app = express(); Ранее вы, возможно, писали это так:
function foo() {
// ...
}
module.exports = foo; В TypeScript вы можете смоделировать это с помощью конструкции export =.
function foo() {
// ...
}
export = foo; Слишком много/слишком мало аргументов
Иногда вы можете обнаружить, что вызываете функцию с слишком большим/недостаточным количеством аргументов. Обычно это ошибка, но в некоторых случаях вы можете объявить функцию, которая использует объект arguments вместо написания параметров:
function myCoolFunction() {
if (arguments.length == 2 && !Array.isArray(arguments[1])) {
var f = arguments[0];
var arr = arguments[1];
// ...
}
// ...
}
myCoolFunction(
function (x) {
console.log(x);
},
[1, 2, 3, 4]
);
myCoolFunction(
function (x) {
console.log(x);
},
1,
2,
3,
4
); В этом случае нам нужно использовать TypeScript для того, чтобы сообщить любым нашим вызывающим сторонам о способах вызова myCoolFunction с использованием перегрузок функций.
function myCoolFunction(f: (x: number) => void, nums: number[]): void;
function myCoolFunction(f: (x: number) => void, ...nums: number[]): void;
function myCoolFunction() {
if (arguments.length == 2 && !Array.isArray(arguments[1])) {
var f = arguments[0];
var arr = arguments[1];
// ...
}
// ...
} Мы добавили две сигнатуры перегрузки в myCoolFunction. Первая проверяет, что myCoolFunction принимает функцию (которая принимает number), а затем список number. Вторая утверждает, что также примет функцию и затем использует rest параметр (...nums) для указания, что любое количество аргументов после этого должны быть number.
Последовательно добавляемые свойства
Некоторым людям кажется более эстетичным создавать объект и добавлять свойства сразу же, как это:
var options = {};
options.color = "red";
options.volume = 11; TypeScript сообщит, что нельзя присвоить значение color и volume, потому что сначала определил тип options как {}, у которого нет свойств. Если вы переместите объявления в сам объект-литерал, ошибок не будет:
let options = {
color: "red",
volume: 11,
}; Вы также можете определить тип options и добавить утверждение типа к объекту-литералу.
interface Options {
color: string;
volume: number;
}
let options = {} as Options;
options.color = "red";
options.volume = 11; В качестве альтернативы, можно просто сказать, что options имеет тип any, что является самым простым, но наименее эффективным решением.
any, Object, и {}
Возможно, вы захотите использовать Object или {}, чтобы указать, что значение может иметь любое свойство, так как Object — в большинстве случаев — самый общий тип. Однако any — это тот тип, который следует использовать в таких ситуациях, так как он самый гибкий.
Например, если у вас есть значение, типизированное как Object, вы не сможете вызвать методы, такие как toLowerCase(), на нём. Более общий тип обычно означает, что с ним можно делать меньше, но any является особым, так как это самый общий тип, позволяющий выполнять с ним любые действия. Это означает, что вы можете вызывать его, создавать объекты с ним, получать доступ к свойствам и так далее. Однако помните, что при использовании any, вы теряете большую часть проверок ошибок и поддержки редактора, которые предоставляет TypeScript.
Если вам нужно выбрать между Object и {}, следует предпочесть {}. Хотя они в основном одинаковы, технически {} является более общим типом, чем Object в некоторых экзотических случаях.
Усиление проверок
TypeScript имеет определённые проверки, чтобы обеспечить большую безопасность и анализ вашей программы. После преобразования кодовой базы в TypeScript вы можете начать включение этих проверок для повышения безопасности.
Отсутствие неявного any
Существуют случаи, когда TypeScript не может определить тип некоторых значений. Для максимальной гибкости он использует тип any. Хотя это удобно при миграции, использование any означает, что вы не получаете никакой типовой безопасности и не получите той же поддержки инструментов, которая доступна в других случаях. Вы можете указать TypeScript, чтобы он отмечал такие места ошибками, с помощью опции noImplicitAny.
Строгие проверки на null и undefined
По умолчанию TypeScript предполагает, что null и undefined относятся ко всем типам. Это означает, что любое значение, объявленное как number может быть null или undefined. Поскольку null и undefined — частая причина ошибок в JavaScript и TypeScript, TypeScript имеет опцию strictNullChecks, чтобы избавить вас от необходимости беспокоиться об этих проблемах.
Когда опция strictNullChecks включена, null и undefined получают свои собственные типы, называемые null и undefined соответственно. В случае, если значение возможно null, вы можете использовать объединение типов с исходным типом. Например, если значение может быть number или null, вы запишете тип как number | null.
Если у вас есть значение, которое TypeScript считает возможным null/undefined, но вы знаете лучше, вы можете использовать постфиксный оператор ! чтобы сообщить об этом.
declare var foo: string[] | null; foo.length; // error - 'foo' is possibly 'null' foo!.length; // okay - 'foo!' just has type 'string[]'
Обратите внимание, что при использовании strictNullChecks может потребоваться обновить зависимости, чтобы использовать strictNullChecks также.
Отсутствие неявного any для this
Когда вы используете ключевое слово this вне классов, оно по умолчанию имеет тип any. Например, представьте класс Point, и предположим, что мы хотим добавить функцию в качестве метода:
class Point {
constructor(public x, public y) {}
getDistance(p: Point) {
let dx = p.x - this.x;
let dy = p.y - this.y;
return Math.sqrt(dx ** 2 + dy ** 2);
}
}
// ...
// Reopen the interface.
interface Point {
distanceFromOrigin(): number;
}
Point.prototype.distanceFromOrigin = function () {
return this.getDistance({ x: 0, y: 0 });
}; Это те же проблемы, о которых мы говорили выше — мы могли бы легко неправильно написать getDistance и не получить об этом ошибки. По этой причине TypeScript имеет опцию noImplicitThis. Когда эта опция включена, TypeScript выдаст ошибку, если this используется без явного (или выведенного) типа. Исправление заключается в использовании параметра this для явного указания типа в интерфейсе или в самой функции:
Point.prototype.distanceFromOrigin = function (this: Point) {
return this.getDistance({ x: 0, y: 0 });
};
© 2012-2023 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/migrating-from-javascript.html