BACK
Featured image of post 【Typescript】tsconfig.json 的設定與資料型別介紹筆記

【Typescript】tsconfig.json 的設定與資料型別介紹筆記

簡單說明一下 typescript 的 tsconfig.json,並記錄一下 typescript 各個資料型別。

參考網站
參考網站
參考網站

本篇來簡單說明一下 typescript 的 tsconfig.json,並記錄一下 typescript 各個資料型別。


TypeScript 是什麼?

❓這個應該是大家都有犯的錯誤,下面這張圖你看到錯誤了嗎?

.
.
.

🅰️:就是寫錯字,record 少了個 s,有時候即使報 error 了,你還找不到。

JavaScript 就是一個自由的靈魂,你永遠不知道你接了什麼物件,寫的時候很爽,debug 的時候黑人問號。

✅ 而下面這張就引用了 TypeScript 的 interface 來定義物件的型別,讓你在開發的時候就可以馬上知道錯誤,減少了很多 debug 的時間。


tsconfig.json

tsconfig.json 是 typescript 編譯設定的文件。

在目錄下執行以下指令,產生 tsconfig.json:

1
tsc --init

tsconfig.json 的預設

一開始我們拿到的 tsconfig 檔, 裡面的預設就有這些:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
{
  "compilerOptions": {
    "target": "es5",  // 指定編譯生成的JS版本
    "module": "commonjs", // 指定生成哪種模組
    "strict": true,  // 啟用所有嚴格類型檢查選項
    "esModuleInterop": true,  // 兼容模組導入的方式
    "skipLibCheck": true,     // 不會檢查引入的函式庫檔案       
    "forceConsistentCasingInFileNames": true // 確保檔案的大小寫一致
  }
}

tsconfig.json 還有哪些設定?

除了預設 compilerOptions 的設定外, 還有:

  • compileOnSave: 在最上層設定 compileOnSave 屬性,可以讓 IDE 編輯器在儲存檔案的時候根據 tsconfig.json 重新產生編譯檔案。
  • compilerOptions: 所有的編譯設定都會寫在 compilerOptions 裡, 我們一開始取得的預設設定就在 compilerOptions。
  • include: 指定編譯需要編譯的文件或目錄。
  • exclude: 指定編譯器需要排除的文件或文件夾。如默認排除 node_modules 文件夾。
    • include 及 exclude 最常見的萬用字元包括:
    • *: 表示匹配0至多個字元(不包含分隔符號)
    • ?: 匹配一個相符字元(不包含分隔符號)
    • **/: 表示匹配所有子資料夾
  • extends: 引入其他配置檔案,繼承配置。default 包含當前目錄和子目錄下所有 TypeScript 文件。
  • files: files 的值為包含相對或絕對文件路徑的 list,表示需要編譯的文件。default 包含當前目錄和子目錄下所有 TypeScript 文件。若指定 files,則只會編譯指定的檔案。
  • references: 指定要引用的專案。有時候為了方便將前端項目和後端 node 項目放在同一個目錄下開發,兩個項目依賴同一個配置文件和通用文件。
  • typeAcquisition: 設置自動引入函式庫相關定義文件。包含 3 個子屬性:
    • enable: 布林類型,是否開啟自動引入庫類型定義文件(.d.ts),默認為 false;
    • include: 數組類型,允許自動引入的函式庫名,如:[“jquery”, “lodash”];
    • exculde: 數組類型,排除的函式庫名;

概括

 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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
{
  "compileOnSave": false, // 編輯器在儲存檔案的時候根據 tsconfig.json 重新產生編譯檔案
  "compilerOptions": {
    /* 基本選項 */
    "target": "es5",      // 指定編譯生成的 JS 版本: 'ES3' (default), 'ES5', 'ES6'/'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'
    "module": "commonjs", // 指定生成哪種模組: 'commonjs', 'amd', 'system', 'umd' or 'es2015'
    "lib": [],            // 編譯需要引入的特定函式庫檔案
    "allowJs": true,      // 允許編譯 javascript 文件
    "checkJs": true,      // 報告 javascript 文件中的錯誤
    "jsx": "preserve",    // 指定 jsx 代碼的生成: 'preserve', 'react-native', or 'react'
    "declaration": true,  // 生成相應的 '.d.ts' 文件
    "declarationMap": true, // 生成宣告檔案的 sourceMap
    "sourceMap": true,   // 生成相應的 '.map' 文件
    "outFile": "./",     // 將輸出文件合併為一個文件
    "outDir": "./",      // 指定輸出目錄
    "rootDir": "./",     // 檔案應該要被放置的位置
    "composite": true,       // 是否編譯構建引用項目
    "tsBuildInfoFile": "./", // 指定文件存儲增量編譯信息,默認為 tsconfig.tsbuildinfo
    "removeComments": true,  // 刪除編譯後的所有的註釋
    "noEmit": true,          // 不產生輸出檔案
    "importHelpers": true,   // 從 tslib 導入輔助工具函數
    "isolatedModules": true,  // 將每個文件做為單獨的 module(與 'ts.transpileModule' 類似)

    /* 嚴格的類型檢查選項 */
    "strict": true,                 // 啟用所有嚴格類型檢查選項                    
    "noImplicitAny": true,          // 在表達式和聲明上有隱含的 any 類型時報錯                   
    "strictNullChecks": true,       // 啟用嚴格的 null 檢查                  
    "strictFunctionTypes": true,    // 啟用檢查 function 型別        
    "strictBindCallApply": true,    // 啟用對 bind、call、apply 更嚴格的型別檢查           
    "strictPropertyInitialization": true,     // 啟用 class 實例屬性的賦值檢查
    "noImplicitThis": true,         // 當 this 表達式值為 any 類型的時候,生成一個錯誤                
    "alwaysStrict": true,           // 以嚴格模式檢查每個 module,並在每個文件裡加入 'use strict'                  

    /* 額外的檢查 */
    "noUnusedLocals": true,             // 有未使用的變數時,拋出錯誤
    "noUnusedParameters": true,         // 有未使用的參數時,拋出錯誤
    "noImplicitReturns": true,          // 並不是所有 function 裡的代碼都有返回值時,拋出錯誤
    "noFallthroughCasesInSwitch": true, // 報告 switch 語句的 fallthrough 錯誤。 (即不允許 switch 的 case 語句貫穿)
    "noUncheckedIndexedAccess": true,   // 檢查 index signature 屬性是否是 undefined     

    /* 模組選項 */
    "moduleResolution": "node",             // 選擇模組解析策略: 'node' (Node.js) or 'classic' (TypeScript pre-1.6)
    "baseUrl": "./",                        // 用於解析非相對模組名稱的基目錄
    "paths": {},                            // 模組名到基於 baseUrl 的路徑映射的列表
    "rootDirs": [],                         // 根文件夾列表,其組合內容表示項目運行時的結構內容
    "typeRoots": [],                        // 包含類型聲明的文件列表
    "types": [],                            // 需要包含的類型聲明文件名列表
    "allowSyntheticDefaultImports": true,   // 允許從沒有設置默認導出的模組中默認導入

    /* Source Map Options */
    "sourceRoot": "./",                    // 指定調試器應該找到 TypeScript 文件而不是源文件的位置
    "mapRoot": "./",                       // 指定調試器應該找到映射文件而不是生成文件的位置
    "inlineSourceMap": true,               // 生成單個 soucemaps 文件,而不是將 sourcemaps 生成不同的文件
    "inlineSources": true,                 // 將代碼與 sourcemaps 生成到一個文件中,要求同時設置了 --inlineSourceMap 或 --sourceMap 屬性

    /* 其他選項 */
    "experimentalDecorators": true,           // 啟用裝飾器
    "emitDecoratorMetadata": true,            // 為裝飾器提供元數據的支持
        
    /* 進階選項 */
    "skipLibCheck": true,                     // 不會檢查引入的函式庫檔案
    "forceConsistentCasingInFileNames": true  // 確保檔案的大小寫一致
  },
  "files":[
    "hello.ts"                        // 若指定 files,則只會編譯指定的 hello.ts 檔案
  ],
  "exclude": [                        // 指定編譯器需要排除的文件或文件夾
    "node_modules"
  ],
  "extends": "./tsconfig.base.json",  // 把基礎配置抽離成 tsconfig.base.json 檔案,然後引入
  "references": [                     // 指定依賴的程式路徑
    { "path": "./common" }
  ],
  "typeAcquisition": {                // 自動引入函式庫相關定義文件(.d.ts)。
    "enable": false,
    "exclude": ["jquery"],
    "include": ["jest"]
  }
}

TypeScript 資料型別整理表

先總結一些資料型別,接著再來詳細介紹各個型別。

Type型別分類說明Example
stringprimitive字串型別
numberprimitive數值型別
booleanprimitive布林型別
nullprimitive空值型別,亦可賦值給所有型別(嚴謹模式則無法)
undefinedprimitiveundefined 型別,亦可賦值給所有型別(嚴謹模式則無法)
objectobject物件型別
arrayobject可使用「型別 + 方括號」或陣列泛型來表示陣列
functionobject一個函式有輸入和輸出,可以針對參數(輸入)及返回值(輸出)進行型別規範
anyts允許賦值為任意型別
unknowntsunknown 和 any 一樣可以接受任何型別賦值,但 any 可以賦值給任何型別,unknown 只能賦值給 any 和自己
voidts沒有任何返回值的函式
neverts表示不應該存在的狀態的型別,一般用於錯誤處理函式
union typests聯合型別(union type) 使用 表示其定義的值可以為多種型別
intersection typests交集型別(intersection type) 使用 & 表示其定義的值都必須符合多種型別
literal typests某些特殊的"值"可以當作"型別"來使用
tupletstuple 就是合併了不同型別的物件
enumts列舉(enum)型別可以用來管理多個同系列的常數,作為狀態判斷使用

primitive: 原始型別 ts: typescript 的特殊型別


typescript 的特殊型別

any

如果你不希望某些值出現型別檢查錯誤,可以使用 any,他是用來表示允許賦值為任意型別。

❌ 假設我們設定 favoriteNumber 變數的值的型別為 string,若使用數字則會報錯。

1
2
3
let favoriteNumber: string = 'seven';
favoriteNumber = 7;
//error : Cannot assign to 'favoriteNumber' because it is a constant.

✅ 這時候使用 any, 則允許任何型別。

1
2
let favoriteNumber: any = 'seven';
favoriteNumber = 7;

宣告一個變數為任意值之後,對它的任何操作,返回的內容的型別都是任意值。在 any 型別下,可以賦值給任何型別,使用任何屬性和方法,都是被允許的。

但這樣是非常危險,非必要都不會使用 any,要慎用!

未宣告型別的變數及參數也視為 any 型別

變數如果在宣告的時候,未指定其型別,那麼它會被識別為 any 型別:

1
2
3
let something; // something: any
something = 7;
something = "seven";

用 noImplicitAny,來提醒我使用了 any

如上面所述,未指定型別則被識為 any,如果你想避免這種情況,可以在 tsconfig 設定 noImplicitAny = true,或是打開 “strict”: true 開啟所有嚴謹模式。他就會提醒你使用了 any 型別。

1
2
3
4
5
{
  // ...
  "noImplicitAny": true,
  // ...
}

unknown

unknow 可以接受任何型別賦值, 有點類似 any 類型, 但使用上比 any 安全,來看下面例子:

1
2
3
4
5
6
7
function f1(a: any) {
  a.b(); // success
}

function f2(a: unknown) {
  a.b(); // error: Property 'b' does not exist on type 'unknown'.
}

使用 unknow 會報錯,沒有 b 函式的屬性,而 any 則會不會告訴你。

雖然 unknown 和 any 一樣可以接受任何型別賦值,但 any 可以賦值給任何型別,unknown 只能賦值給 any 和自己。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let value: unknown;

let value1: unknown = value; // success
let value2: any = value; // success
let value3: boolean = value; // error
let value4: number = value; // error
let value5: string = value; // error
let value6: object = value; // error
let value7: any[] = value; // error
let value8: Function = value; // error

void

JavaScript 沒有空值(void)的概念,在 TypeScript 中,可以用 void 表示沒有任何返回值的函式,如以下例子,這個函式只有 alert,不會 return 任何值。

1
2
3
function alertName(): void {
  alert('My name is iris');
}

never

表示不應該存在的狀態的型別,一般用於錯誤處理函式。

1
2
3
function error(message: string): never {
  throw new Error(message);
}

此外,以下面例子,參數使用 Union Types(聯合型別),當參數被判斷沒有其他型別時,也會被視為 never。

1
2
3
4
5
6
7
8
9
function fn(x: string | number) {
  if (typeof x === "string") {
    // do something
  } else if (typeof x === "number") {
    // do something else
  } else {
    x; // has type 'never'!
  }
}

Union Types (聯合型別)

聯合型別(Union Types)表示取值可以為多種型別中的其中一種。跟JavaScript || or 的概念是一樣的。

✅ 聯合型別使用 | 分隔每個型別。

允許 id 的型別是 string 或者 number,但是不能是其他型別。

1
2
3
4
5
function printId(id: number | string) {
  console.log("Your ID is: " + id);
}
printId(101); // success
printId("202"); // success

❌ 若使用 boolean 值,就會報錯。

1
2
printId(false); // error
// error : Argument of type 'false' is not assignable to parameter of type 'string | number'.Type 'false' is not assignable to type 'number'.

存取聯合型別的屬性或方法

當 TypeScript 不確定一個聯合型別的變數到底是哪個型別的時候,我們只能存取此聯合型別的所有型別裡共有的屬性或方法。

✅ 使用兩種型別都有的屬性時,沒問題。

1
2
3
function printId(id: number | string) {
  console.log(id.toString());
}

❌ 如果使用不同屬性,則會報錯,toUpperCase 屬性無法適用於數值型別。

1
2
3
4
5
6
function printId(id: number | string) {
  console.log(id.toUpperCase());
}

// error: Property 'toUpperCase' does not exist on type 'string | number'.
// Property 'toUpperCase' does not exist on type 'number'.

若要解決不同情境,可以使用 typeof 去做判斷。官網將這些情況限縮某個具體型別上的行為,稱作為 Narrowing

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
function printId2(id: number | string) {
  if (typeof id === "string") {
    // 型別為字串才 toUpperCase
    console.log(id.toUpperCase());
  } else {
    // 其他自動判定為 number 型別
    console.log(id);
  }
}
printId2("ABC");

若是陣列型別與其他型別聯合,可以使用 Array.isArray() 來檢查傳入的值是否為一個 Array。

1
2
3
4
5
6
7
8
function welcomePeople(x: string[] | string) {
  if (Array.isArray(x)) {
    console.log("Hello, " + x.join(" and "));
  } else {
    console.log("Welcome lone traveler " + x);
  }
}
welcomePeople(["a","b","c"]);

Intersection Types (交集型別)

Intersection Types (交集型別) 跟 JavaScript 的 && and 概念相同, 使用 & 表示其定義的值都必須同時符合兩種型別。

❌ 以下面的例子,Intersection 在 primitive type(原始型別)中使用,是無法同時滿足兩種型別的,會被認定為 never 型別。

1
2
3
4
5
6
7
function printId(id: number & string) {
  console.log("Your ID is: " + id);
}
printId(101); // error
printId("202"); // error

// error: Argument of type 'number' is not assignable to parameter of type 'never'.

✅ 主要用來組合現有的型別,若都沒符合兩種型別,則會報錯提醒。而且像下面的例子,ColorfulCircle 需滿足 Colorful 及 Circle 型別。 而且 TS 很聰明,radius 寫錯字寫成 raidus,complier 也會報錯提醒!

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
interface Colorful {
  color: string;
}

interface Circle {
  radius: number;
}

//用 type aliases 宣告 ColorfulCircle 型別,需滿足 Colorful 及 Circle 型別
type ColorfulCircle = Colorful & Circle;

//帶入的參數需滿足 ColorfulCircle 型別
function draw(circle: ColorfulCircle) {
  console.log(`Color was ${circle.color}`);
  console.log(`Radius was ${circle.radius}`);
}

draw({ color: "blue", radius: 42 }); // ok
draw({ color: "red", raidus: 42 }); // error
// error: Argument of type '{ color: string; raidus: number; }' is not assignable to parameter of type 'Colorful & Circle'.
// Object literal may only specify known properties, but 'raidus' does not exist in type 'Colorful & Circle'. Did you mean to write 'radius'?

Literal Types 字面值型別

string literal types

就是值的表現方式,**某些特殊的"值"可以當作"型別"**來使用,用來約束取值只能是某幾個字串中的一個。如下方讓 x 變數的字面值為"hello"。

1
2
3
let x: "hello" = "hello";
x = "hello"; // success
x = "howdy";  // Type '"howdy"' is not assignable to type '"hello"'

但很少情況會使用到一個變數只有一個值,我們可以將想要的值結合起來, 如 alignment 給予 3 個字面值,其參數要符合其中一個。如果不符合這 3 個值的其中一個或寫錯字,都會報錯。

1
2
3
4
5
6
function printText(s: string, alignment: "left" | "right" | "center") {
  console.log(`${s} placed at the ${alignment}`)
}
printText("Hello, world", "left"); // success
printText("G'day, mate", "centre"); // error
// error: Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.

numeric literal types

除了文字上,數字的操作也一樣。

執行 compare 函式的回傳值只能 -1、0 和 1:

1
2
3
function compare(a: string, b: string): -1 | 0 | 1 {
  return a === b ? 0 : a > b ? 1 : -1;
}

non-literal types

非字面值型別也可以結合操作:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
interface Options {
  width: number;
}
function configure(x: Options | "auto") {
  console.log(x);
}
configure({ width: 100 }); // success
configure("auto"); // success
configure("automatic"); // 不符 Options 及 "auto"
// error: Argument of type '"automatic"' is not assignable to parameter of type 'Options | "auto"'.

Tuple 元組

Tuple 就是合併了不同型別的物件。

定義一對值分別為 string 和 number 的元組:

1
const wayne: [string, number] = ['wayne', 18];

可以針對型別有的屬性進行使用、賦值。但當直接對元組型別的變數進行初始化或者賦值,需提供所有項目。如果要新增不同型別的項目也是無法的:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
let wayne: [string, number];
wayne = ['wayne', 18]; // 如果只有宣告 wayne 沒賦值,會是undefined,tsconfig strictNullChecks 打開的話會報錯提醒
wayne[0] = 'wayne'; // success
wayne[1] = 25; // success
wayne[0].slice(1); // success
wayne[1].toFixed(2); // success
wayne.push('male'); // success

wayne = ['wayne lin']; // error: Property '1' is missing in type '[string]' but required in type '[string, number]'.
wayne.push(true); // error: Argument of type 'true' is not assignable to parameter of type 'string | number'.

enum

列舉(Enums)型別用於取值被限定在一定範圍內的場景,可以用來管理多個同系列的常數,作為狀態判斷使用。比如一週只能有七天,顏色限定為紅綠藍等。也可以自定義來表示每一個值。

Numeric Enums

使用 enum 關鍵字來定義:

1
enum Days { Sun, Mon, Tue, Wed, Thu, Fri, Sat };

這時候我們來看編譯後,列舉成員會被賦值為從 0 開始自動遞增的數字,同時也會對列舉值到列舉名進行反向對映:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var Days;
(function (Days) {
  Days[Days["Sun"] = 0] = "Sun";
  Days[Days["Mon"] = 1] = "Mon";
  Days[Days["Tue"] = 2] = "Tue";
  Days[Days["Wed"] = 3] = "Wed";
  Days[Days["Thu"] = 4] = "Thu";
  Days[Days["Fri"] = 5] = "Fri";
  Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

手動賦值

可以手動去定義不同的值。

1
enum Days { Sun = 7, Mon = 1, Tue, Wed, Thu, Fri, Sat };

編譯後:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var Days;
(function (Days) {
  Days[Days["Sun"] = 7] = "Sun";
  Days[Days["Mon"] = 1] = "Mon";
  Days[Days["Tue"] = 2] = "Tue";
  Days[Days["Wed"] = 3] = "Wed";
  Days[Days["Thu"] = 4] = "Thu";
  Days[Days["Fri"] = 5] = "Fri";
  Days[Days["Sat"] = 6] = "Sat";
})(Days || (Days = {}));

要小心覆蓋的情況

如果中間突然指定了了一個值,那他會從當前的值從新開始計算。如下, Days[3] 的值先是 “Sun”,而後又被 “Wed” 覆蓋了。這個 TypeScript 是不會報錯的,所以要特別注意。

1
2
3
4
5
6
enum Days { Sun = 3, Mon = 1, Tue, Wed, Thu, Fri, Sat };

console.log(Days["Sun"] === 3); // true
console.log(Days["Wed"] === 3); // true
console.log(Days[3] === "Sun"); // false
console.log(Days[3] === "Wed"); // true

可使用型別斷言(Type Assertion)來定義

手動賦值的列舉項其中一個不想是數字,此時可以使用 any 型別斷言來讓 TypeScript 無視型別檢查。

1
enum Days { Sun = 7, Mon, Tue, Wed, Thu, Fri, Sat = <any>"S" };

編譯後:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
var Days;
(function (Days) {
  Days[Days["Sun"] = 7] = "Sun";
  Days[Days["Mon"] = 8] = "Mon";
  Days[Days["Tue"] = 9] = "Tue";
  Days[Days["Wed"] = 10] = "Wed";
  Days[Days["Thu"] = 11] = "Thu";
  Days[Days["Fri"] = 12] = "Fri";
  Days[Days["Sat"] = "S"] = "Sat";
})(Days || (Days = {}));

String Enums 字串列舉

字串 enums 沒有 number enums 可以自動遞增的行為,但字串 enum 自行定義是有他的語意在的。

1
2
3
4
5
6
enum Direction {
  Up = "UP",
  Down = "DOWN",
  Left = "LEFT",
  Right = "RIGHT",
}

Heterogeneous Enums 異構列舉

雖然 enum 可以混合數字與字串,但盡量不要寫這種寫法:

1
2
3
4
enum BooleanLikeHeterogeneousEnum {
  No = 0,
  Yes = "YES",
}

Computed and constant members 常數項和計算所得項

列舉項有兩種型別:常數項(constant member)和計算所得項(computed member)。

前面我們所舉的例子都是常數項,一個典型的計算所得項的例子如下,“blue”.length 就是一個計算所得項:

1
2
enum Color { Red, Green, Blue = "blue".length };
console.log(Color.Blue); // 4

如何定義是常數項,可參考官網

像是二元運算子 <<|& 等都歸為常數項:

1
2
3
4
5
6
7
8
9
enum FileAccess {
  // constant members
  None,
  Read = 1 << 1,
  Write = 1 << 2,
  ReadWrite = Read | Write,
  // computed member
  G = "123".length,
}

Const Enums 常數列舉

可以使用 const enum 定義的列舉型別:

1
2
3
4
5
6
7
8
const enum Directions {
  Up,
  Down,
  Left,
  Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

常數列舉與普通列舉的區別是,它會在編譯階段被刪除,並且不能包含 computed member (計算成員),只能是常數項 (constant member)。

編譯後:

1
var directions = [0 /* Up */, 1 /* Down */, 2 /* Left */, 3 /* Right */];

Ambient Enums 外部列舉

外部列舉 (Ambient Enums) 是使用 declare enum 定義的列舉型別:

1
2
3
4
5
6
7
8
declare enum Directions {
  Up,
  Down,
  Left,
  Right
}

let directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

declare 定義的型別只會用於編譯時的檢查,編譯結果中會被刪除。

編譯後:

1
var directions = [Directions.Up, Directions.Down, Directions.Left, Directions.Right];

comments powered by Disqus