|
| 1 | +--- |
| 2 | +language: TypeScript |
| 3 | +contributors: |
| 4 | + - ["Philippe Vlérick", "https://github.com/pvlerick"] |
| 5 | + - ["Kiwimoe", "https://github.com/kiwimoe"] |
| 6 | +translators: |
| 7 | + - ["Woody Chang", "https://github.com/kazettique"] |
| 8 | +filename: learntypescript-zh-tw.ts |
| 9 | +lang: zh-tw |
| 10 | +--- |
| 11 | + |
| 12 | +TypeScript 是為開發大型 JavaScript 應用程式而設計的語言。它為 JavaScript 導入某些程式語言常見的一些概念,諸如:類別(class)、模組(module)、介面(interface)、泛型(generic type)和靜態型別(static type)。TypeScript 是 JavaScript 的「超集」(superset):意即建立在 JavaScript 的基礎上,所有 JavaScript 語法皆可在 TypeScript 中使用。因此,TypeScript 可以無縫導入到任何 JavaScript 專案中。TypeScript 編譯器最終會編譯成 JavaScript 程式碼。 |
| 13 | + |
| 14 | +本文將只專注於 TypeScript 的額外語法,其他請參考 [JavaScript 的指南](/docs/javascript-tw) |
| 15 | + |
| 16 | +要測試 TypeScript 的編譯器,請前往 [Playground](https://www.typescriptlang.org/play),在那裡你可以輸入程式碼,獲得自動完成(autocomplete)功能,並查看編譯過的 JavaScript 程式碼。 |
| 17 | + |
| 18 | +```ts |
| 19 | +// TS 基本型別有三種 |
| 20 | +let isDone: boolean = false; |
| 21 | +let lines: number = 42; |
| 22 | +let name: string = "Anders"; |
| 23 | + |
| 24 | +// 當變數有賦值時,也可以省略型別定義 |
| 25 | +let isDone = false; |
| 26 | +let lines = 42; |
| 27 | +let name = "Anders"; |
| 28 | + |
| 29 | +// 若無法確定型別,則可以定義為 `any` |
| 30 | +let notSure: any = 4; |
| 31 | +notSure = "maybe a string instead"; |
| 32 | +notSure = false; // 布林值也屬於 `any` 型別 |
| 33 | + |
| 34 | +// 以 `const` 關鍵字定義常數 |
| 35 | +const numLivesForCat = 9; |
| 36 | +numLivesForCat = 1; // 報錯,常數初始化之後,無法指定新值 |
| 37 | + |
| 38 | +// 關於集合類型的資料,有型別化陣列(typed array)和泛型陣列(generic array) |
| 39 | +let list: number[] = [1, 2, 3]; |
| 40 | +// 或使用泛型陣列類型 |
| 41 | +let list: Array<number> = [1, 2, 3]; |
| 42 | + |
| 43 | +// 列舉型別: |
| 44 | +enum Color { Red, Green, Blue }; |
| 45 | +let c: Color = Color.Green; |
| 46 | +console.log(Color[c]); // "Green" |
| 47 | + |
| 48 | +// `void` 用於函式不回傳任何值的特殊情況 |
| 49 | +function bigHorribleAlert(): void { |
| 50 | + alert("I'm a little annoying box!"); |
| 51 | +} |
| 52 | + |
| 53 | +// 函式是一等公民,支援 lambda「胖箭頭」 `=>` 語法,並使用型別推斷 |
| 54 | + |
| 55 | +// 以下幾種函式寫法是等效的,編譯器會生成相同的 JavaScript 程式碼 |
| 56 | +// 一般的函式 |
| 57 | +let f1 = function (i: number): number { return i * i; } |
| 58 | +// 自動推斷回傳型別 |
| 59 | +let f2 = function (i: number) { return i * i; } |
| 60 | +// 使用胖箭頭語法 |
| 61 | +let f3 = (i: number): number => { return i * i; } |
| 62 | +// 胖箭頭語法(自動推斷回傳型別) |
| 63 | +let f4 = (i: number) => { return i * i; } |
| 64 | +// 胖箭頭語法(自動推斷回傳型別、省略函式的括號與 `return` 關鍵字) |
| 65 | +let f5 = (i: number) => i * i; |
| 66 | + |
| 67 | +// 函式的參數也可以同時定義多種型別的連集 |
| 68 | +function f6(i: string | number): void { |
| 69 | + console.log("The value was " + i); |
| 70 | +} |
| 71 | + |
| 72 | +// 介面是結構化的,任何擁有這些屬性的物件都要符合該介面的定義 |
| 73 | +interface Person { |
| 74 | + name: string; |
| 75 | + // 以問號(`?`)來表示選填的屬性 |
| 76 | + age?: number; |
| 77 | + // 當然也可以包含函式 |
| 78 | + move(): void; |
| 79 | +} |
| 80 | + |
| 81 | +// 實作 `Person` 介面的物件 |
| 82 | +// 可被視為一個 `Person` 物件,因為它具有 `name` 和 `move` 屬性 |
| 83 | +let p: Person = { name: "Bobby", move: () => { } }; |
| 84 | +// 包含選填屬性的物件: |
| 85 | +let validPerson: Person = { name: "Bobby", age: 42, move: () => { } }; |
| 86 | +// 此物件非 `Person` 物件,因為 `age` 屬性非數字 |
| 87 | +let invalidPerson: Person = { name: "Bobby", age: true }; |
| 88 | + |
| 89 | +// 介面也可以描述一個函式的型別 |
| 90 | +interface SearchFunc { |
| 91 | + (source: string, subString: string): boolean; |
| 92 | +} |
| 93 | +// 函式的型別定義著重於各個參數以及回傳值的型別,而函式名稱並不重要 |
| 94 | +let mySearch: SearchFunc; |
| 95 | +mySearch = function (src: string, sub: string) { |
| 96 | + return src.search(sub) != -1; |
| 97 | +} |
| 98 | + |
| 99 | +// 類別的屬性,其存取權限預設為公開(public) |
| 100 | +class Point { |
| 101 | + // 定義屬性 |
| 102 | + x: number; |
| 103 | + |
| 104 | + // 在建構函式種使用 `public`、`private` 關鍵字,會實例化的時候自動生成屬性 |
| 105 | + // 以此為例,`y` 如同 `x` 定義其屬性,並於實例化時賦值,但寫法更為簡潔,同時支援預設值 |
| 106 | + constructor(x: number, public y: number = 0) { |
| 107 | + this.x = x; |
| 108 | + } |
| 109 | + |
| 110 | + // 函式,在類別中,又稱為方法(method) |
| 111 | + dist(): number { |
| 112 | + return Math.sqrt(this.x * this.x + this.y * this.y); |
| 113 | + } |
| 114 | + |
| 115 | + // 靜態成員(static member) |
| 116 | + static origin = new Point(0, 0); |
| 117 | +} |
| 118 | + |
| 119 | +// 類別可以被明確標記為實作某個介面。 |
| 120 | +// 任何缺少的屬性或方法都會在編譯時引發錯誤。 |
| 121 | +class PointPerson implements Person { |
| 122 | + name: string |
| 123 | + move() {} |
| 124 | +} |
| 125 | + |
| 126 | +let p1 = new Point(10, 20); |
| 127 | +let p2 = new Point(25); // y 值將預設為 0 |
| 128 | + |
| 129 | +// 類別的繼承 |
| 130 | +class Point3D extends Point { |
| 131 | + constructor(x: number, y: number, public z: number = 0) { |
| 132 | + super(x, y); // 必須明確呼叫父類別的建構函式,使用 `super` 關鍵字 |
| 133 | + } |
| 134 | + |
| 135 | + // 複寫父類別的方法 |
| 136 | + dist(): number { |
| 137 | + let d = super.dist(); |
| 138 | + return Math.sqrt(d * d + this.z * this.z); |
| 139 | + } |
| 140 | +} |
| 141 | + |
| 142 | +// 模組,以 `.` 語法存取子模組 |
| 143 | +module Geometry { |
| 144 | + export class Square { |
| 145 | + constructor(public sideLength: number = 0) { |
| 146 | + } |
| 147 | + area() { |
| 148 | + return Math.pow(this.sideLength, 2); |
| 149 | + } |
| 150 | + } |
| 151 | +} |
| 152 | + |
| 153 | +let s1 = new Geometry.Square(5); |
| 154 | + |
| 155 | +// 引用模組,可以在本地使用別名命名並使用之 |
| 156 | +import G = Geometry; |
| 157 | + |
| 158 | +let s2 = new G.Square(10); |
| 159 | + |
| 160 | +// 泛用型別,泛型(generic type) |
| 161 | +// 在類別使用泛型 |
| 162 | +class Tuple<T1, T2> { |
| 163 | + constructor(public item1: T1, public item2: T2) { |
| 164 | + } |
| 165 | +} |
| 166 | + |
| 167 | +// 在介面使用泛型 |
| 168 | +interface Pair<T> { |
| 169 | + item1: T; |
| 170 | + item2: T; |
| 171 | +} |
| 172 | + |
| 173 | +// 在函式使用泛型 |
| 174 | +let pairToTuple = function <T>(p: Pair<T>) { |
| 175 | + return new Tuple(p.item1, p.item2); |
| 176 | +}; |
| 177 | + |
| 178 | +let tuple = pairToTuple({ item1: "hello", item2: "world" }); |
| 179 | + |
| 180 | +// 引用型別定義檔: |
| 181 | +/// <reference path="jquery.d.ts" /> |
| 182 | + |
| 183 | +// 樣板字串(template string)(使用反引號「`」的字串) |
| 184 | +// 以樣板字串進行字串內插(interpolation) |
| 185 | +let name = 'Tyrone'; |
| 186 | +let greeting = `Hi ${name}, how are you?` |
| 187 | +// 多行的樣板字串 |
| 188 | +let multiline = `This is an example |
| 189 | +of a multiline string`; |
| 190 | + |
| 191 | +// 唯讀存取子:TypeScript 3.1 的新語法 |
| 192 | +interface Person { |
| 193 | + readonly name: string; |
| 194 | + readonly age: number; |
| 195 | +} |
| 196 | + |
| 197 | +var p1: Person = { name: "Tyrone", age: 42 }; |
| 198 | +p1.age = 25; // 錯誤,`p1.age` 為唯讀屬性 |
| 199 | + |
| 200 | +var p2 = { name: "John", age: 60 }; |
| 201 | +var p3: Person = p2; // 正確,`p2` 的唯讀別名 |
| 202 | +p3.age = 35; // 錯誤,`p3.age` 為唯讀屬性 |
| 203 | +p2.age = 45; // 正確,但因為 `p3` 參照可 `p2`,因此 `p3.age` 將會被修改 |
| 204 | + |
| 205 | +class Car { |
| 206 | + readonly make: string; |
| 207 | + readonly model: string; |
| 208 | + readonly year = 2018; |
| 209 | + |
| 210 | + constructor() { |
| 211 | + this.make = "Unknown Make"; // 唯讀屬性在建構函式被允許賦值 |
| 212 | + this.model = "Unknown Model"; // 唯讀屬性在建構函式被允許賦值 |
| 213 | + } |
| 214 | +} |
| 215 | + |
| 216 | +let numbers: Array<number> = [0, 1, 2, 3, 4]; |
| 217 | +let moreNumbers: ReadonlyArray<number> = numbers; |
| 218 | +moreNumbers[5] = 5; // 錯誤,陣列的成員為唯讀 |
| 219 | +moreNumbers.push(5); // 錯誤,無 `push` 方法(因為 `push` 方法會改變陣列的值) |
| 220 | +moreNumbers.length = 3; // 錯誤,`length` 為唯讀 |
| 221 | +numbers = moreNumbers; // 錯誤,修改陣列的方法並不存在於唯讀陣列 |
| 222 | + |
| 223 | +// 可以使用聯合型別(union type)來定義不同的資料型別 |
| 224 | +type State = |
| 225 | + | { type: "loading" } |
| 226 | + | { type: "success", value: number } |
| 227 | + | { type: "error", message: string }; |
| 228 | + |
| 229 | +declare const state: State; |
| 230 | +if (state.type === "success") { |
| 231 | + console.log(state.value); |
| 232 | +} else if (state.type === "error") { |
| 233 | + console.error(state.message); |
| 234 | +} |
| 235 | + |
| 236 | +// 樣板實字(template literal)型別 |
| 237 | +// 可以用來建立複雜的字串型別 |
| 238 | +type OrderSize = "regular" | "large"; |
| 239 | +type OrderItem = "Espresso" | "Cappuccino"; |
| 240 | +type Order = `A ${OrderSize} ${OrderItem}`; |
| 241 | + |
| 242 | +let order1: Order = "A regular Cappuccino"; |
| 243 | +let order2: Order = "A large Espresso"; |
| 244 | +let order3: Order = "A small Espresso"; // 錯誤 |
| 245 | + |
| 246 | +// 迭代器(iterator)與產生器(generator) |
| 247 | + |
| 248 | +// for..of 陳述式 |
| 249 | +// 循覽物件的每個成員「值」(value) |
| 250 | +let arrayOfAnyType = [1, "string", false]; |
| 251 | +for (const val of arrayOfAnyType) { |
| 252 | + console.log(val); // 1, "string", false |
| 253 | +} |
| 254 | + |
| 255 | +let list = [4, 5, 6]; |
| 256 | +for (const i of list) { |
| 257 | + console.log(i); // 4, 5, 6 |
| 258 | +} |
| 259 | + |
| 260 | +// for..in 陳述式 |
| 261 | +// 循覽物件的每個成員的「鍵」(key) |
| 262 | +for (const i in list) { |
| 263 | + console.log(i); // 0, 1, 2 |
| 264 | +} |
| 265 | + |
| 266 | +// 型別斷言(assertion) |
| 267 | +let foo = {} // 建立一個名為 `foo` 的空物件 |
| 268 | +foo.bar = 123 // 錯誤,`bar` 屬性並不存在於 `{}` |
| 269 | +foo.baz = 'hello world' // 錯誤:`baz` 屬性並不存在於 `{}` |
| 270 | + |
| 271 | +// 因為 `foo` 的推斷型別是 `{}`(一個無任何屬性的物件),所以不允許新增 `bar`、`baz` 及其他任何名稱的屬性。然而,通過型別斷言,以下程式碼將能夠通過 TS 的檢查: |
| 272 | +interface Foo { |
| 273 | + bar: number; |
| 274 | + baz: string; |
| 275 | +} |
| 276 | + |
| 277 | +let foo = {} as Foo; // 這裡使用型別斷言 |
| 278 | +foo.bar = 123; |
| 279 | +foo.baz = 'hello world' |
| 280 | +``` |
| 281 | + |
| 282 | +## 延伸閱讀 |
| 283 | + |
| 284 | +* [TypeScript官網](https://www.typescriptlang.org/) |
| 285 | +* [TypeScript原始碼](https://github.com/microsoft/TypeScript) |
| 286 | +* [Learn TypeScript](https://learntypescript.dev/) |
0 commit comments