Typescript-Migrating from Javascript

翻譯來源

Migrating from JavaScript

從 Javascript 搬移你的程式到 TypeScript

TypeScript 不會憑空存在, 他還是依存於 Javascript 的生態圈內,有很多舊的 Javascript 要轉譯為 TypeScript 過程中很無趣的。

如果你是要轉譯 React project 我們會推薦你先閱讀這份文件

設定你的資料結構

基本的檔案架構會如下

1
2
3
4
5
6
projectRoot
├── src
│ ├── file1.js
│ └── file2.js
├── built
└── tsconfig.json

如果你有想要用測試的話,在 src 再加上 tests 並且在 tsconfig.json 除了 src 之外再加上 tests

寫一個設定檔案

TypeScript 使用 tsconfig.json 來做專案的設定

1
2
3
4
5
6
7
8
9
10
{
"compilerOptions": {
"outDir": "./built",
"allowJs": true,
"target": "es5"
},
"include": [
"./src/**/*"
]
}

我們利用這個設定檔案 對 TypeScript 做一些設定

  1. 包含 include 讀取 src 中所有檔案
  2. 允許 Javascript 文件直接輸入
  3. 可以在 build 輸出所有編譯後文件
  4. 將 ES6或是ES7 轉譯為 ES5

如果你是使用 tsc 轉譯你的專案,你應該會在 built 資料夾裡面看到編譯成功的檔案

優勢

如果你使用的是VS code 或是 Visual Studio 你可以使用相當多的工具,例如自動完成。也可以增加一些設定方便你 debug

  • noImplicitReturns 可以防止你在 function 的最後忘記回傳值
  • noFallthroughCasesInSwitch 可以協助你補上 breakswitch 中的 case

TypeScript 依舊會對無法訪問的標籤的錯誤顯示,你可以利用 allowUnreachableCodeallowUnusedLabels 來取消

整合你的編譯工具

每個人都有自己的編譯步驟

下個範例是我們覺得目前最佳的方式

Gulp

相關的 gulp 使用可以參考我們另外一個文件

Webpack

Webpack 是一個相當簡單的工具!

你可以使用 awesome-typescript-loader 這是一個 TypeScript Loader

另外也可以使用 source-map-loader 讓你更易於 debug

1
$ yarn add awesome-typescript-loader source-map-loader

將上述兩個套件加入你的 webpack.config.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
module.exports = {
entry: './src/index.ts'
output: {
filename: './dist/bundle.js'
},

devtool: 'source-map',

resolve: {
extensions: ['', 'webpack.js', '.web.js', '.ts', '.tsx', 'js']
},

moudle: {
loaders: [
{test: /\.tsx?$/, loader: 'awesome-typescript-loader'}
],

preLoaders: [
{test: /\.js$/, loader: 'source-map-loader'}
]
}
}

awesome-typescript-load 必須在所有的 loader 的前面 ts-loader 也是一樣的道理,你可以在這邊獲得更多資訊

將 .js 轉換成為 .ts

我們可以開始對檔案做一些動作開始轉換

第一個步驟就是將所有的 .js 換成 .ts

若是你的檔案有使用 JSX 則需要將檔案名稱換成 .tsx

當然你會覺得怪怪的,這樣就結束了麻?

當然不是!

接著你可以打開你的編輯器或是使用 command line

1
$ tsc --pretty

你可以看到一些 紅色 的波浪底線

這些就像是 微軟的 word 軟體提醒你這些並不符合 TypeScript 的規範

如果這些對你說太寬鬆,你希望可以更加嚴謹的話你也可以使用 noEmitOnError 這一個選項來讓檢查更加嚴謹

如果你希望使用嚴謹模式可以參考這篇文章

例如你不希望變數型態使用不明確的 any 你可以在編輯檔案之前使用 noImplicitAny

解決錯誤相關問題

就像剛剛提到的,你會修改 .js.ts 或是 .tsx 的時候會有相當多的錯誤訊息

你會發現這些錯誤雖然是屬於合法的錯誤,但是透過這些錯誤可以發現 TypeScript 對你開發程式碼的好處。

import 模組

你可能會得到錯誤訊息是 Cannot find name 'require' 或是 Cannot find name 'deffined'

在這些狀況在這些狀況應該是 TypeScript 找不到這些模組

你需要預先選擇使用引入模組的方式,可以使用 commonjs, amd, systemumd

如果你有使用 Nod/CommonJS

1
2
var foo = require('foo');
foo.doStuff();

或是 RequireJS/AMD

1
2
3
define(["foo"], function(foo){
foo.doStuff();
});

你可以修改為

1
2
import foo = require('foo');
foo.doStuff();
TypeScript 的宣告

如果你編譯檔案的時候有安裝 foo module

但是依然看到 Cannot find module 'foo'

這個錯誤訊息,很有可能是你並未有宣告的檔案來宣告你的 library,

要處理這個問題也很簡單

1
$ yarn add @types/lodash -D

如果你有使用 module 選項是 commonjs 你另外需要設定 moduleResolutionnode

然後你才可以正常沒有錯誤訊息的引用 lodash

Export 模組

通常在 export 模組的時候都是使用 export 或是 module.exports

TypeScript 允許你使用 export 參數,例如:

1
2
3
module.exports.feedPets = function(pets){
//....
}

你可以改為

1
2
3
export function feedPets(pets){
//...
}

有時候你會直接複寫 exports object

這是屬於 commonJS 的設計

在引用這類的模組的時候

1
2
var express = require('express');
var app = express();

你在之前會有像這樣的範例程式

1
2
3
4
function foo(){
//...
}
module.exports = foo;

TypeScript 你可以修改為 export = consturct

1
2
3
4
function foo(){
//...
}
export = foo;

有時候你呼叫函式的時候會有太多或是太少的參數,

通常這是一種 bug

但是在有些狀況中你可以宣告函示使用 arguments object 來代替寫出的所有參數

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
function myCoolFunction() {
if (arguments.length == 2 && Array.isArray(arguments[1])) {
var f = arguments[0];
var arr = arguments[1];

arr.map(value => f(value));
} else {
var f = arguments[0];
var arr = arguments[1];
console.log("arguments: ", arguments);
}
}

myCoolFunction(
function(x) {
console.log(x);
},
[1, 2, 3, 4]
);
myCoolFunction(
function(x) {
console.log(x);
},
1,
2,
3,
4
);

result:

1
2
3
4
5
1
2
3
4
arguments: { '0': [Function], '1': 1, '2': 2, '3': 3, '4': 4 }

在這個範例中因為有兩種 input 方式

所以在 TypeScript 需要改為

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
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];

arr.map(value => f(value));
} else {
var f = arguments[0];
var arr = arguments[1];
console.log("arguments: ", arguments);
}
}

myCoolFunction(
function(x) {
console.log(x);
},
[1, 2, 3, 4]
);
myCoolFunction(
function(x) {
console.log(x);
},
1,
2,
3,
4
);
1
$ tsc src/demo1.ts && node src/demo1.js

result

1
2
3
4
5
1
2
3
4
arguments: { '0': [Function], '1': 1, '2': 2, '3': 3, '4': 4 }

TypeScript 中宣告了 myCoolFunction 兩個不同的宣告,

定義一個 f 為 function : (x: number) => void

然後也對多個變數使用 ...nums: number[] 來宣告是一個數字陣列

增加參數

有些人對一個物件增加參數使用下述方式

1
2
3
let options = {};
options.color = 'red';
options.volume = 11;

TypeScript 則會表示這樣無法新增 color 和 volume 因為 property 不存在

1
2
3
4
5
6
7
interface Options {color: string, volume: number};

var options = {}; // Error
var options = {} as Options; // Success

options.color = 'red';
options.volume = 11;

你可以將 type 定義為 object 或是 {} 這樣也可以自由地增加物件特性,但是比較常使用的是 any 來宣告這類型自由的型態

例如你有一個物件類型宣告為 object 你將無法呼叫 toLowerCase() 這個函式,但是若是你宣告的是 any 的類型的話,就可以呼叫這個函式。也就是當你宣告為 any 這個類型的話就不會有太多的錯誤提示你的型別問題

嚴謹模式

TypeScript 可以幫你檢查並且分析你的程式,讓你的錯誤減少

不推薦使用 any

使用了 any 這個類型會造成遇到錯誤時 TypeScript 也不會提醒你,也就喪失使用 TypeScript 的意義,你可以設定 noImplicitAny 這個選項來控制禁止使用 any

更嚴謹的檢查 undefined 和 null

TypeScriptundefinednull 的類型宣告都是 any,但是 undefinednullJavascriptTypeScript 造成許多的 Bug,所以你可以設定 strictNullChecks 來做更嚴謹的檢查。

當你設定 strictNullChecks 的時候 undefinednull 也會擁有他們自己的類型 undefinednull , 若是有些變數同時可能是數字或是 null 的時候,可以利用 union 來宣告類型, 例如

1
2
3
4
declare var foo: string[] | null;

foo.length; // error - 'foo' 有可能是null
foo!.length; // okay - foo! 只可能是 'string[]'

不可宣告 this 為 any

當你使用 this 這個keyword 的時候,預設是 any,例如

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
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);
}
}

interface Point{
distanceFromOrigin(point: Point): number;
}

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

這部分就像我們上面談的問題其實是一樣的

我們沒辦法去檢查 this 這個變數的型態

TypeScript 也有一個 noImplicitThis 的選項

當這個選項被設定為 true 的時候, TypeScript 會提醒你在使用 this 時要設定型態

1
2
3
Poing.prototype.distanceFromOrigin = function(this: Point, point: Point){
return this.getDistance({x: 0, y: 0});
}
文章目录
  1. 1. 翻譯來源
  2. 2. 從 Javascript 搬移你的程式到 TypeScript
    1. 2.1. 設定你的資料結構
    2. 2.2. 寫一個設定檔案
      1. 2.2.1. 優勢
      2. 2.2.2. 整合你的編譯工具
        1. 2.2.2.1. Gulp
        2. 2.2.2.2. Webpack
      3. 2.2.3. 將 .js 轉換成為 .ts
        1. 2.2.3.1. 解決錯誤相關問題
          1. 2.2.3.1.1. import 模組
          2. 2.2.3.1.2. TypeScript 的宣告
          3. 2.2.3.1.3. Export 模組
          4. 2.2.3.1.4. 增加參數
          5. 2.2.3.1.5. 嚴謹模式
            1. 2.2.3.1.5.1. 不推薦使用 any
          6. 2.2.3.1.6. 更嚴謹的檢查 undefined 和 null
        2. 2.2.3.2. 不可宣告 this 為 any
|