從 JavaScript 遷移

TypeScript 並非獨立存在。它是考量 JavaScript 生態系統所建置的,而現今有許多 JavaScript。將 JavaScript 程式碼庫轉換為 TypeScript 雖然有點繁瑣,但通常並不困難。在本教學課程中,我們將探討如何開始。我們假設您已閱讀足夠的手冊來撰寫新的 TypeScript 程式碼。

如果您想轉換 React 專案,我們建議您先查看 React 轉換指南

設定您的目錄

如果您使用純 JavaScript 編寫,您很可能直接執行您的 JavaScript,其中您的 .js 檔案位於 srclibdist 目錄中,然後依需要執行。

如果是這樣,您編寫的檔案將作為 TypeScript 的輸入,而您將執行它產生的輸出。在我們的 JS 到 TS 的遷移過程中,我們需要將輸入檔案分開,以防止 TypeScript 覆寫它們。如果您的輸出檔案需要駐留在特定目錄中,那麼那將是您的輸出目錄。

您也可能對您的 JavaScript 執行一些中間步驟,例如綑綁或使用另一個轉譯器,例如 Babel。在這種情況下,您可能已經設定了類似這樣的資料夾結構。

從這一點開始,我們假設您的目錄設定如下

projectRoot ├── src │ ├── file1.js │ └── file2.js ├── built └── tsconfig.json

如果您在 src 目錄之外有一個 tests 資料夾,您可能在 src 中有一個 tsconfig.json,在 tests 中也有一個。

撰寫組態檔

TypeScript 使用名為 tsconfig.json 的檔案來管理專案的選項,例如包含哪些檔案,以及要執行哪些檢查。讓我們為專案建立一個精簡的版本

json
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "es5"
},
"include": ["./src/**/*"]
}

我們在此向 TypeScript 指定幾件事

  1. 讀取 src 目錄中它了解的任何檔案(使用 include)。
  2. 接受 JavaScript 檔案作為輸入(使用 allowJs)。
  3. built 中發出所有輸出檔案(使用 outDir)。
  4. 將較新的 JavaScript 結構轉換為較舊版本,例如 ECMAScript 5(使用 target)。

此時,如果您嘗試在專案的根目錄執行 tsc,您應該會在 built 目錄中看到輸出檔案。built 中的檔案配置應與 src 的配置相同。現在您應該讓 TypeScript 與您的專案搭配使用。

早期優點

即使在這個階段,您也能從 TypeScript 了解您的專案中獲得一些極大的優點。如果您開啟像 VS CodeVisual Studio 這樣的編輯器,您會看到您通常可以獲得一些工具支援,例如完成。您也可以使用以下選項來偵測某些錯誤

TypeScript 也會警告無法到達的程式碼和標籤,你可以分別使用 allowUnreachableCodeallowUnusedLabels 來停用這些警告。

與建置工具整合

你的管線中可能還有其他建置步驟。也許你會將某些內容串接至每個檔案。每個建置工具都不同,但我們會盡力說明重點。

Gulp

如果你以某種方式使用 Gulp,我們有一個關於 使用 Gulp 搭配 TypeScript,並與常見的建置工具(例如 Browserify、Babelify 和 Uglify)整合的教學課程。你可以前往該處閱讀更多資訊。

Webpack

Webpack 整合非常簡單。你可以使用 TypeScript 載入器 ts-loader,搭配 source-map-loader 以簡化偵錯。只要執行

shell
npm install ts-loader source-map-loader

並將下列選項合併到你的 webpack.config.js 檔案中

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 檔案的其他載入器之前執行。

你可以在我們的 React 和 Webpack 教學課程 中看到使用 Webpack 的範例。

轉移到 TypeScript 檔案

在這個階段,您可能已準備好開始使用 TypeScript 檔案。第一步是將其中一個 .js 檔案重新命名為 .ts。如果您的檔案使用 JSX,您需要將其重新命名為 .tsx

完成該步驟了嗎?太棒了!您已成功將檔案從 JavaScript 移轉到 TypeScript!

當然,這可能感覺不對勁。如果您在支援 TypeScript 的編輯器中開啟該檔案(或執行 tsc --pretty),您可能會在某些行看到紅色波浪線。您應該將它們視為與在 Microsoft Word 等編輯器中看到紅色波浪線相同的方式。TypeScript 仍會轉譯您的程式碼,就像 Word 仍會讓您列印文件一樣。

如果這聽起來對您來說太寬鬆,您可以加強這種行為。例如,如果您希望 TypeScript 在出現錯誤時編譯成 JavaScript,您可以使用 noEmitOnError 選項。在這個意義上,TypeScript 的嚴格性可以調整,您可以根據需要將旋鈕調高。

如果您計畫使用現有的更嚴格設定,最好現在就將它們開啟(請參閱下方的 取得更嚴格的檢查)。例如,如果您不希望 TypeScript 在您未明確說明的情況下自動推斷 any 的型別,您可以在開始修改檔案之前使用 noImplicitAny。雖然這可能會讓人有點不知所措,但長期收益會顯得更快。

清除錯誤

如我們所提到的,在轉換後收到錯誤訊息並非意外。重要的是要逐一檢視這些錯誤,並決定如何處理它們。這些錯誤通常是合法的錯誤,但有時您必須向 TypeScript 更好地解釋您想做什麼。

從模組匯入

你可能會開始收到一堆錯誤,例如 找不到名稱 'require'。找不到名稱 'define'。。在這些情況下,你很可能正在使用模組。雖然你可以透過寫出以下內容來說服 TypeScript 它們存在

ts
// For Node/CommonJS
declare function require(path: string): any;

ts
// For RequireJS/AMD
declare function define(...args: any[]): any;

但最好擺脫那些呼叫並使用 TypeScript 語法進行匯入。

首先,你需要透過設定 TypeScript 的 module 選項來啟用一些模組系統。有效的選項包括 commonjsamdsystemumd

如果你有以下 Node/CommonJS 程式碼

js
var foo = require("foo");
foo.doStuff();

或以下 RequireJS/AMD 程式碼

js
define(["foo"], function (foo) {
foo.doStuff();
});

那麼你會寫出以下 TypeScript 程式碼

ts
import foo = require("foo");
foo.doStuff();

取得宣告檔案

如果你開始轉換為 TypeScript 匯入,你可能會遇到 找不到模組 'foo'。 之類的錯誤。這裡的問題是你可能沒有宣告檔案來描述你的程式庫。幸運的是,這很容易。如果 TypeScript 抱怨像 lodash 這樣的套件,你可以寫

shell
npm install -S @types/lodash

如果你使用的是 commonjs 以外的模組選項,你需要將你的 moduleResolution 選項設定為 node

在那之後,你將能夠毫無問題地匯入 lodash,並獲得準確的完成。

從模組匯出

通常,從模組匯出包含將屬性新增至值,例如 exportsmodule.exports。TypeScript 允許您使用頂層匯出陳述式。例如,如果您匯出一個函式如下

js
module.exports.feedPets = function (pets) {
// ...
};

您可以將其寫成如下

ts
export function feedPets(pets) {
// ...
}

有時您會完全覆寫 exports 物件。這是一個常見的模式,人們使用它來讓他們的模組可以立即呼叫,如下面的片段所示

js
var express = require("express");
var app = express();

您可能以前寫過如下

js
function foo() {
// ...
}
module.exports = foo;

在 TypeScript 中,您可以使用 export = 建構來建模。

ts
function foo() {
// ...
}
export = foo;

太多/太少參數

您有時會發現自己使用太多/太少的參數呼叫函式。通常,這是一個錯誤,但在某些情況下,您可能已經宣告一個函式,該函式使用 arguments 物件,而不是寫出任何參數

js
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 可以使用函式重載呼叫的方式。

ts
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

順序新增的屬性

有些人認為這樣建立物件並立即新增屬性,在美觀上比較令人滿意

js
var options = {};
options.color = "red";
options.volume = 11;

TypeScript 會說你無法指定 colorvolume,因為它首先將 options 的類型設定為 {},而 {} 沒有任何屬性。如果你將宣告移到物件文字本身,你就不會收到任何錯誤

ts
let options = {
color: "red",
volume: 11,
};

你也可以定義 options 的類型,並在物件文字中新增類型斷言。

ts
interface Options {
color: string;
volume: number;
}
let options = {} as Options;
options.color = "red";
options.volume = 11;

或者,你也可以直接說 options 的類型是 any,這是最簡單的方法,但對你的幫助最小。

anyObject{}

你可能會想使用 Object{} 來表示一個值可以擁有任何屬性,因為 Object 在大多數情況下是最通用的類型。然而,any 才是你實際上想在這種情況下使用的類型,因為它是最靈活的類型。

例如,如果你有一個類型為 Object 的東西,你將無法對它呼叫像 toLowerCase() 這樣的函式。通常,更通用表示你可以使用類型做的事情更少,但 any 很特別,因為它是最通用的類型,同時仍然允許你對它做任何事情。這表示你可以呼叫它、建構它、存取它的屬性,等等。不過請記住,每當你使用 any 時,你都會失去 TypeScript 提供給你的大部分錯誤檢查和編輯器支援。

如果一個決定歸結為 Object{},你應該優先選擇 {}。雖然它們大部分相同,但技術上在某些深奧的情況下,{} 是比 Object 更通用的類型。

取得更嚴格的檢查

TypeScript 附帶某些檢查,可提供更安全的程式和分析。一旦將程式碼庫轉換為 TypeScript,就可以開始啟用這些檢查以獲得更高的安全性。

沒有隱含的 any

在某些情況下,TypeScript 無法找出某些類型應該是什麼。為了盡可能寬鬆,它會決定使用類型 any 來代替。雖然這對於遷移來說很好,但使用 any 表示您沒有獲得任何類型安全性,而且您不會獲得在其他地方獲得的相同工具支援。您可以告訴 TypeScript 標記這些位置並使用 noImplicitAny 選項給出錯誤。

嚴格的 nullundefined 檢查

預設情況下,TypeScript 假設 nullundefined 在每個類型的網域中。這表示宣告為類型 number 的任何東西都可能是 nullundefined。由於 nullundefined 是 JavaScript 和 TypeScript 中錯誤的常見來源,因此 TypeScript 有 strictNullChecks 選項,讓您不必擔心這些問題。

strictNullChecks 已啟用時,nullundefined 會取得自己的類型,分別稱為 nullundefined。每當任何東西可能null 時,您可以使用與原始類型結合的聯合類型。因此,例如,如果某個東西可能是 numbernull,您會將類型寫成 number | null

如果您有任何值,而 TypeScript 認為它可能是 null/undefined,但您更了解它,您可以使用後置 ! 算子來告訴它。

ts
declare var foo: string[] | null;
foo.length; // error - 'foo' is possibly 'null'
foo!.length; // okay - 'foo!' just has type 'string[]'

提醒您,當使用 strictNullChecks 時,您的相依性可能需要更新才能使用 strictNullChecks

this 的隱含 any

當您在類別外部使用 this 關鍵字時,它預設具有 any 類型。例如,想像一個 Point 類別,並想像一個我們希望新增為方法的函式

ts
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 參數來提供明確類型

ts
Point.prototype.distanceFromOrigin = function (this: Point) {
return this.getDistance({ x: 0, y: 0 });
};

TypeScript 文件是一個開放原始碼專案。透過 傳送 Pull Request ❤ 來協助我們改善這些頁面

此頁面的貢獻者
DRDaniel Rosenwasser (57)
OTOrta Therox (14)
TAThomas Ankcorn (3)
MGMaayan Glikser (3)
MF馬丁·費雪 (1)
19+

最後更新:2024 年 3 月 21 日