【譯】2019年開始使用Typescript

call_me_R發表於2019-02-17

banner

基於2018年Stack Overflow Developer的調研TypeScript作為程式語言比JavaScript更受“喜愛”。TypeScript在js開發者中這麼受喜愛的原因是:在你執行程式碼前,新增到javascript中的型別有助你發現錯誤(程式碼)。TypeScript編譯器提供的錯誤可以很好的引導你如何修復程式碼錯誤。往javascript中新增型別同時有助程式碼編輯器提供一些高階的功能,例如程式碼完成,專案範圍的重構和自動模組的匯入。

more loved programming languages

如果你認為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:buildtsc:watch)。

vs code build tasks menu

瞭解靜態和動態型別

JavaScript附帶7種動態型別:

  • Undefined
  • Null
  • Boolean
  • Number
  • String
  • Symbol
  • Object

上面的型別被稱為動態型別,因為它們在執行時使用。

TypeScript為JavaScript語言帶來了靜態型別,並且這些型別在編譯時(無需執行程式碼)被確定。靜態型別可以預測動態型別的值,這可以幫助在無需執行程式碼的情況下警告你可能出現的錯誤。

基本靜態型別

好吧,我們來深入研究下TypeScript的語法。以下是TypeScript中最常見的型別。

備註:我遺漏了never和object型別,因為根據我的經驗,它們並不被經常使用。

boolean

你已經很瞭解truefalse值了。

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)是由numbernull型別組成的,因此TypeScript只希望numbernull型別的值加入陣列。

型別註釋

型別推斷系統不夠用的時,你需要在變數和物件上宣告型別。

基本型別

在(上面)基本靜態型別章節的介紹中,所有的型別都使用:後跟型別名來宣告。

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 aliasinterface的比較的話,我推薦你看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函式的第二個引數是一個字串,因此建立的陣列將其所有元素設定為具有字串型別。

應該注意的是,按照慣例,單個(大寫)字母用於泛型型別(比如:TK)。可是,並不限制你使用更具有描述性的名稱來表示你的泛型型別。下面示例就是為所提供的泛型型別使用了更具有描述性的名稱:

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)

原文連結 www.robertcooper.me/get-started…

文章首發 github.com/reng99/blog…

更多內容 github.com/reng99/blog…

相關文章