基於2018年Stack Overflow Developer的調研,TypeScript
作為程式語言比JavaScript更受“喜愛”。TypeScript在js開發者中這麼受喜愛的原因是:在你執行程式碼前,新增到javascript中的型別有助你發現錯誤(程式碼)。TypeScript編譯器提供的錯誤可以很好的引導你如何修復程式碼錯誤。往javascript中新增型別同時有助程式碼編輯器提供一些高階的功能,例如程式碼完成,專案範圍的重構和自動模組的匯入。
如果你認為TypeScript是一門全新的程式語言,那麼學習它可能令人生畏。然而,TypeScript只是JavaScript的一個附加層(超集),在使用TypeScript前,你無需瞭解它的每個語法。TypeScript允許你通過更改檔案的字尾名.js
為.ts
來輕鬆的轉換javascript檔案,並且所有的程式碼將作為TypeScript來正確編譯。如果你想在TypeScript檔案中強制執行更廣的型別覆蓋百分比,你可以將TypeScript配置得更具侷限性,一旦你熟悉該語言了,你就可以完成此操作。
本文旨在帶你快速瞭解一個標準的TypeScript專案中會遇到的95%的場景。剩餘的5%,嗯,你可以google,還有,我會在本文底部放些有用的TypeScript資源連結。
配置TypeScript
當然,要開始編寫能正確編譯的TypeScript(檔案),正確配置開發環境是必要的。
1、安裝TypeScript編譯器
首先,為了能夠將TypeScript檔案轉換成JavaScript檔案,我們需要安裝TypeScript編譯器。安裝TypeScript可全域性安裝(檔案系統中安裝,可以在任何位置使用)或者本地安裝(僅在專案級別可使用)。【個人偏向後者】
# NPM Installation Method
npm install --global typescript # Global installation
npm install --save-dev typescript # Local installation
# Yarn Installation Method
yarn global add typescript # Global installation
yarn add --dev typescript # Local installation
複製程式碼
2、確保你的編輯器設定為支援TypeScript
你需要確保正確配置了你的編輯器以使用TypeScript。比如,為了在編輯器中能更好得使用TypeScript,你需要安裝一個外掛(如果你使用atom,你可以安裝 atom-typescript)。如果你使用的是VS Code編輯器
,那麼你不需要安裝額外的外掛了,因為它內建了TypeScript的支援。?
3、新建tsconfig.json檔案
tsconfig.json檔案是用來配置TypeScript專案設定。它應該放在專案的根目錄中。該檔案允許你使用不同的選項配置TypeScript編譯器。
如果你僅僅是想TypeScript生效的話,你只需要tsconfig.json檔案中包含一個空JSON物件,但是,如果你需要TypeScript編譯器的有不同的行為(比如在特定的輸出目錄中輸出編譯後的JavaScript檔案),你可以閱讀更多有關可以配置哪些設定的(內容)。
備註:你也可以通過執行
tsc --init
去生成一個tsconfig.json檔案,其中為你設定了些預設選項,還有一些被註釋掉的其他選項。
4、將TypeScript轉化為JavaScript
為了將你的TypeScript程式碼轉化成JavaScript程式碼,需要在控制檯上跑tsc命令。執行tsc命令將告訴TypeScript編譯器去搜尋tsconfig.json
檔案,該檔案將確定專案的根目錄以及編譯TypeScript並將.ts
檔案轉換為.js
檔案時用的選項。
為了快速驗證設定生效,你可以建立一個測試的TypeScript檔案,然後在命令列中執行tsc
,之後檢視下TypeScript檔案旁邊是否生成了JavaScript檔案。
舉個例子,TypeScript檔案如下...
const greeting = (person: string) => {
console.log('Good day ' + person);
};
greeting('Daniel');
複製程式碼
應該被轉換為下面這個JavaScript檔案了...
var greeting = function(person) {
console.log('Good day ' + person);
};
greeting('Daniel');
複製程式碼
如果你想TypeScript編譯器(動態)監視TypeScript檔案內容的變更,並自動將.ts
檔案轉換成.js
檔案,你可以在你專案的倉庫(命令列)中執行tsc -p
。
在VS Code(編輯器)中,你可以使用⌘⇧B調出一個選單,該選單(包含)可以在正常模式和監視模式下執行轉換程式(分別對應tsc:build
和tsc:watch
)。
瞭解靜態和動態型別
JavaScript附帶7種動態型別:
- Undefined
- Null
- Boolean
- Number
- String
- Symbol
- Object
上面的型別被稱為動態型別,因為它們在執行時使用。
TypeScript為JavaScript語言帶來了靜態型別,並且這些型別在編譯時(無需執行程式碼)被確定。靜態型別可以預測動態型別的值,這可以幫助在無需執行程式碼的情況下警告你可能出現的錯誤。
基本靜態型別
好吧,我們來深入研究下TypeScript的語法。以下是TypeScript中最常見的型別。
備註:我遺漏了never和object型別,因為根據我的經驗,它們並不被經常使用。
boolean
你已經很瞭解true
和false
值了。
let isAwesome: boolean = true;
複製程式碼
string
文字資料用單引號('')或雙引號("")或後標記(``)【也稱模版字元】包圍。
let name: string = 'Chris';
let breed: string = 'Border Collie';
複製程式碼
如果你使用後標誌,該字串被稱為模版文字,可以在裡面插入表示式。
let punchline: string = 'Because it was free-range.';
let joke: string = `
Q: Why did the chicken cross the road?
A: ${punchline}
`;
複製程式碼
number
任何浮點數都給定為數字型別。作為TypeScript的一部分,支援的四種型別的數字文字是二進位制,十進位制,八進位制和十六進位制。
let decimalNumber: number = 42;
let binaryNumber: number = 0b101010; // => 42
let octalNumber: number = 0o52; // => 42
let hexadecimalNumber: number = 0x2a; // => 42
複製程式碼
備註:並不是只有你一個人對二進位制,八進位制和十六進位制數字感到困惑。
array
TypeScript中有兩種書寫陣列型別的方式。第一種是[]字尾在需要查詢的陣列元素型別。
let myPetFamily: string[] = ['rocket', 'fluffly', 'harry'];
複製程式碼
另一種可替代的方式是,Array後跟要查詢的陣列元素型別的Array
型別(使用尖括號包含)。
let myPetFamily: Array<string> = ['rocket', 'fluffly', 'harry'];
複製程式碼
tuple
元組是一個包含固定數量的元素和相關型別的陣列。
let myFavoriteTuple: [string, number, boolean];
myFavoriteTuple = ['chair', 20, true]; // ✅
myFavoriteTuple = [5, 20, true]; // ❌ - The first element should be a string, not a number
複製程式碼
enum
列舉將名稱和常量值關聯,可以是數字或者字串。當你想一組具有關聯性的描述名稱的不同值,列舉就很有用處了。
預設,為列舉分配從0開始的值,接下來的值為(上一個列舉值)加1。
enum Sizes {
Small,
Medium,
Large,
}
Sizes.Small; // => 0
Sizes.Medium; // => 1
Sizes.Large; // => 2
複製程式碼
第一個值也可以設定為非0的值。
enum Sizes {
Small = 1,
Medium,
Large,
}
Sizes.Small; // => 1
Sizes.Medium; // => 2
Sizes.Large; // => 3
複製程式碼
列舉預設是被分配數字,然而,字串也可以被分配到一個列舉中的。
enum ThemeColors {
Primary = 'primary',
Secondary = 'secondary',
Dark = 'dark',
DarkSecondary = 'darkSecondary',
}
複製程式碼
any
如果變數的型別未知,並且我們並不希望型別檢查器在編譯時抱怨,則可以使用any
型別。
let whoKnows: any = 4; // assigned a number
whoKnows = 'a beautiful string'; // can be reassigned to a string
whoKnows = false; // can be reassigned to a boolean
複製程式碼
在開始使用TypeScript的時,可能會頻繁使用any
型別。然而,最好嘗試減少any
的使用,因為當編譯器不知道與變數相關的型別時,TypeScript的有用性會降低。
void
當沒有與事物相關型別的時候,void
型別應該被使用。在指定不返回任何內容的函式返回值時,最常用它。
const darkestPlaceOnEarth = (): void => {
console.log('Marianas Trench');
};
複製程式碼
null和undefined
null和undefined都對應你在javascript中看到的null和undefined值的型別。這些型別在單獨使用的時候不是很有用。
let anUndefinedVariable: undefined = undefined;
let aNullVariable: null = null;
複製程式碼
預設情況下,null和undefined型別是其他型別的子型別,這意味著可以為string型別的變數賦值為null或者undefined。這通常是不合理的行為,所以通常建議將tsconfig.json
檔案中的strictNullChecks編譯器選項設定為true。將strictNullChecks設定為true,會使null和undefined需要顯示設定為變數的型別。
型別推斷
幸運的是,你不需要在程式碼中全部位置指定型別,因為TypeScript具有型別推斷。型別推斷是TypeScript編譯器用來自行決定型別的(內容)。
基本型別推斷
TypeScript可以在變數初始化期間,設定預設引數以及確定函式返回值時推斷型別。
// Variable initialization
let x = 10; // x is given the number type
複製程式碼
在上面的例子中,x
被分配了數字,TypeScript會以number
型別將x
變數關聯起來。
// Default function parameters
const tweetLength = (message = 'A default tweet') => {
return message.length;
};
複製程式碼
在上面的例子中,message引數被賦予了一個型別為string的預設值,因此TypeScript編譯器會推斷出message的型別是string,因此在訪問length屬性的時候並不會丟擲編譯錯誤。
function add(a: number, b: number) {
return a + b;
}
const result = add(2, 4);
result.toFixed(2); // ✅
result.length; // ❌ - length is not a property of number types
複製程式碼
在上面這個例子中,因為TypeScript告訴add
函式,它的引數都是number
型別,那麼可以推斷得出返回的型別也應該是number
。
最佳通用型別推斷
從多種可能的型別中推斷型別時,TypeScript使用最佳通用型別演算法來選擇適用於所有其他候選型別的型別。
let list = [10, 22, 4, null, 5];
list.push(6); // ✅
list.push(null); // ✅
list.push('nope'); // ❌ - type 'string' is neither of type 'number' or 'null'
複製程式碼
在上面的例子中,陣列(list)是由number
或null
型別組成的,因此TypeScript只希望number
或null
型別的值加入陣列。
型別註釋
當型別推斷系統不夠用的時,你需要在變數和物件上宣告型別。
基本型別
在(上面)基本靜態型別章節的介紹中,所有的型別都使用:
後跟型別名來宣告。
let aBoolean: boolean = true;
let aNumber: number = 10;
let aString: string = 'woohoo';
複製程式碼
Arrays
在(上面)講到的array
型別的章節中,arrays可以通過兩種方式的其中一種進行註釋。
// First method is using the square bracket notation
let messageArray: string[] = ['hello', 'my name is fred', 'bye'];
// Second method uses the Array keyword notation
let messageArray: Array<string> = ['hello', 'my name is fred', 'bye'];
複製程式碼
介面
將多種型別的註釋組合到一起的一種方法是使用介面。
interface Animal {
kind: string;
weight: number;
}
let dog: Animal;
dog = {
kind: 'mammal',
weight: 10,
}; // ✅
dog = {
kind: true,
weight: 10,
}; // ❌ - kind should be a string
複製程式碼
型別別名
TypeScript使用Type Alias指定多個型別註釋,這事(讓人)有些疑惑。【下面講到】
type Animal = {
kind: string;
weight: number;
};
let dog: Animal;
dog = {
kind: 'mammal',
weight: 10,
}; // ✅
dog = {
kind: true,
weight: 10,
}; // ❌ - kind should be a string
複製程式碼
在使用介面或型別別名這方面,最佳的做法似乎是,在程式碼庫保持一致情況下,通常選擇介面型別或型別別名。但是,如果編寫其他人可以使用的第三方的公共API,就要使用介面型別了。
如果你想了解更多關於type alias
和interface
的比較的話,我推薦你看Martin Hochel的這篇文章。
內聯註釋
相比建立一個可複用的介面,有時內聯註釋型別可能更合適。
let dog: {
kind: string;
weight: number;
};
dog = {
kind: 'mammal',
weight: 10,
}; // ✅
dog = {
kind: true,
weight: 10,
}; // ❌ - kind should be a string
複製程式碼
泛型
某些情況下,變數的特定型別無關緊要,但是應強制執行不同變數和型別之間的關係。針對這些情況,應該使用泛型型別。
const fillArray = <T>(len: number, elem: T) => {
return new Array<T>(len).fill(elem);
};
const newArray = fillArray<string>(3, 'hi'); // => ['hi', 'hi', 'hi']
newArray.push('bye'); // ✅
newArray.push(true); // ❌ - only strings can be added to the array
複製程式碼
上面的示例中有一個泛型型別T
,它對應於傳遞給fillArray
函式的第二個引數型別。傳遞給fillArray
函式的第二個引數是一個字串,因此建立的陣列將其所有元素設定為具有字串型別。
應該注意的是,按照慣例,單個(大寫)字母用於泛型型別(比如:T
或K
)。可是,並不限制你使用更具有描述性的名稱來表示你的泛型型別。下面示例就是為所提供的泛型型別使用了更具有描述性的名稱:
const fillArray = <ArrayElementType>(len: number, elem: ArrayElementType) => {
return new Array<ArrayElementType>(len).fill(elem);
};
const newArray = fillArray<string>(3, 'hi'); // => ['hi', 'hi', 'hi']
newArray.push('bye'); // ✅
newArray.push(true); // ❌ - only strings can be added to the array
複製程式碼
聯合型別
在型別可以是多種型別之一的情況下,使用|
分隔符隔開不同型別的選項來使用聯合型別。
// The `name` parameter can be either a string or null
const sayHappyBirthdayOnFacebook = (name: string | null) => {
if (name === null) {
console.log('Happy birthday!');
} else {
console.log(`Happy birthday ${name}!`);
}
};
sayHappyBirthdayOnFacebook(null); // => "Happy birthday!"
sayHappyBirthdayOnFacebook('Jeremy'); // => "Happy birthday Jeremy!"
複製程式碼
交集型別
交集型別使用&
符號將多個型別組合在一起。這和(上面的)聯合型別不同,因為聯合型別是表示結果的型別是列出的型別之一,而交集型別則表示結果的型別是所有列出型別的集合。
type Student = {
id: string;
age: number;
};
type Employee = {
companyId: string;
};
let person: Student & Employee;
person.age = 21; // ✅
person.companyId = 'SP302334'; // ✅
person.id = '10033402'; // ✅
person.name = 'Henry'; // ❌ - name does not exist in Student & Employee
複製程式碼
元組型別
元組型別使用一個:
符號,其後跟一個使用中括號包含且逗號分隔的型別列表表示。
let list: [string, string, number];
list = ['apple', 'banana', 8.75]; // ✅
list = ['apple', true, 8.75]; // ❌ - the second argument should be of type string
list = ['apple', 'banana', 10.33, 3]; // ❌ - the tuple specifies a length of 3, not 4
複製程式碼
可選型別
可能存在函式引數或者物件屬性是可選的情況。在這些情況下,使用?
來表示這些可選值。
// Optional function parameter
function callMom(message?: string) {
if (!message) {
console.log('Hi mom. Love you. Bye.');
} else {
console.log(message);
}
}
// Interface describing an object containing an optional property
interface Person {
name: string;
age: number;
favoriteColor?: string; // This property is optional
}
複製程式碼
有幫助的資源
本文中未涉及到的TypeScript內容,我推薦以下的資源。
TypeScript Handbook (Official TypeScript docs)
TypeScript Deep Dive (Online TypeScript Guide)
Understanding TypeScript's Type Annotation (Great introductory TypeScript article)