สอนวิธีเซ็ตโปรเจค TypeScript / มาใส่ไทป์ให้ JavaScript เพื่อลดความผิดพลาดในการเขียนโค้ดกันดีกว่า
Tags: typescript, javascript, programming, tutorial
Published: 2020-07-28 06:43:35

ภาษา JavaScript นั้นเป็นภาษาที่เริ่มเขียนได้ไม่ยาก ยิ่งถ้าใครเขียนภาษาสาย Structure/OOP เช่น C/C++, C#, Java มาก่อน อาจจะชอบในความเป็น Dynamic Type เราสร้างตัวแปรแล้วเก็บค่าอะไรก็ได้ ทำให้มีความคล่องตัวในการเขียนมากๆ ก่อนที่พอเขียนไปเรื่อยๆ เราจะพบความแปลกของมัน ยิ่งเขียนไปนานๆ เราก็พบว่ามันเป็นภาษาที่ทำให้เกิดบั๊กได้ง่ายมาก และเป็นหนึ่งในภาษาที่น่าปวดหัวที่สุดที่คนโหวตๆ กันเลย ด้วยเหตุผลแรกคือการที่ตัวแปรไม่มี Type นั่นเอง (อีกเหตุผลหนึ่งคือส่วนใหญ่ของคนที่เปลี่ยนมาเขียน JavaScript เคยชินกับภาษาแนว OOP มาก่อน พอมาเขียนภาษาแนว Functional เลยงงหน่อยๆ)

## หลักการคือคอมไพล์ออกมาเป็น JavaScript

เอาจริงๆ ตัวภาษา TypeScript เอาไปรันอะไรไม่ได้เลย 

แต่เราใช้วิธีการคอมไพล์ไฟล์ `.ts` ให้ออกมาเป็นไฟล์ `.js` อีกที หน้าที่ของมันมีแค่นั้นเอง

หรือบางกรณีถ้าเราเขียน React เราอาจจะรู้จักกับ `.jsx` สำหรับ TypeScript ก็มีไฟล์นามสกุล `.tsx` ให้ใช้เช่นกัน

## Installation มาติดตั้ง TypeScript กัน

สำหรับ TypeScript Compiler นั้นสามารถติดตั้งได้ทั้งผ่าน **[npm](https://www.npmjs.com/)** หรือ **[yarn](https://yarnpkg.com)** ด้วยคำสั่งประมาณนี้

```
npm install -g typescript
```

พอติดตั้งแล้วเราสามารถใช้คำสั่ง `tsc` สำหรับคอมไพล์ไฟล์ TypeScript ได้เลย 

วิธีนี้เป็นการติดตั้งแบบ global คือติดตั้งครั้งเดียว ใช้ได้ทุกที่เลย แต่ถ้าอยากติดตั้งแต่ในโปรเจคๆ ไปก็สามารถทำได้ด้วย

```
npm install typescript --save-dev
```

หรือจะใช้งานผ่าน npx ก็ได้เช่นกัน
```
npx tsc
```
flag `--save-dev` หรือตัวย่อ `-D` ใส้ไว้เพื่อระบุว่าเราใช้แพ็กเกจนี้เฉพาะตอน develop เท่านั้น เมื่อไหร่ที่จะ build โปรเจคเอาไปใช้จริง ไม่ต้องเอาติดไปด้วยนะ เพราะอย่างที่บอกคือ TypeScript นั้นตัวมันไม่ได้เอาไปรัน แต่คอมไพล์เป็น JavaScript เมื่อไหร่ที่เขียนเสร็จแล้ว ก็ไม่ต้องใช้ต่อแล้ว






## คอมไพล์ไฟล์ TypeScript

เริ่มแรกลองมาสร้างไฟล์ TypeScript กันดีกว่า

```ts
//app.ts

function sum(n: number): number {
    let sum: number = 0
    for(let i = 0; i < n; i++){
        sum += i
    }
    return sum
}

const total: number = sum(100)

console.log(total)
```
เขียนโค้ดนี้แล้วเซฟเป็นไฟล์ชื่อ `app.ts` ไว้ จะเห็นว่า syntax ทั้งหมดนั้นเหมือนกัน JavaScript ทุกอย่าง ที่เพิ่มเติมมาก็แค่หลังชื่อตัวแปร ตอนที่ประกาศตัวแปรและ parameter เราจะต้องกำหนดชนิดตัวแปรลงไปด้วย โดยใช้ตัว `:` ตามแพทเทิร์นนี้

```
var-name: type
```
ไทป์ที่ใช้ได้ก็เช่น

```ts
let decimal: number = 6
let hex: number = 0xf00d
let binary: number = 0b1010
let octal: number = 0o744

let color: string = "blue"

let isDone: boolean = false
```
สำหรับตัวแปรประเภท Array นั้นประกาศได้ 2 แบบคือ
```ts
let list: number[] = [1, 2, 3]
let list: Array<number> = [1, 2, 3]
```

ส่วนตัวแปรแบบ Object นั้นจะต้องสร้าง `interface` เพื่อกำหนดไทป์ขึ้นมาก่อน

```ts
interface Point {
    x: number
    y: number
}

let myPoint: Point = { x: 10, y: 20 }
```

สำหรับ TypeScript นั้น เราไม่สามารถสร้างตัวแปรแบบไม่กำหนดไทป์ได้ แต่ถ้าเราไม่รู้ไทป์จริงๆ เราสามารถประกาศไทป์ `any` ได้ (แต่ก็ไม่แนะนำให้ทำนะ)

---

จากนั้นเราใช้คำสั่ง `tsc` ในการคอมไพล์ไฟล์ TypeScript ของเรา
```
tsc app.ts
```
หลังจากคอมไพล์เสร็จแล้ว เราจะได้อีกไฟล์เพิ่มมาในชื่อเดียวกันแต่เป็นนามสกุล `.js` แทน


![](https://3.bp.blogspot.com/-56cikDwtKas/XwM8664jOfI/AAAAAAAASdw/TLsnYlIbd74qoxC4OSAxzrN9sYescrudgCNcBGAsYHQ/s1600/ts-03.png)

เมื่อเราเอามาเปิดดูก็จะพบว่ามันมีหน้าตาคล้ายๆ กับโค้ด TypeScript ที่เราเขียนไป แต่ชนิดตัวแปรที่เคยประกาศจะหายไป และอาจจะมีอะไรอีกนิดหน่อยด้วย (เช่นมี `;` เพิ่มเข้ามาให้ จากที่เคยใช้ `let`, `const` ก็เปลี่ยนเป็น `var`) ซึ่งโดยร่วมไม่ได้ทำให้โลจิคเปลี่ยนไปนะ

```js
// app.js

function sum(n) {
    var sum = 0;
    for (var i = 0; i < n; i++) {
        sum += i;
    }
    return sum;
}
var total = sum(100);
console.log(total);

```

ซึ่งจุดนี้อาจจะทำให้คนที่เพิ่งเริ่มใช้ TypeScript สงสัยว่าถ้ามันแค่ตัด Type ทิ้งไป แล้วทำไมเราไม่เขียน JavaScript ตรงๆ ไปเลยล่ะ?

เช่นผลคือ TypeScript สามารถช่วยเราในส่วนของการเช็ก Compiler-Time Error ได้

เช่นโค้ดนี้

![](https://2.bp.blogspot.com/-WHzrsW6_x6M/XwXbfapXk2I/AAAAAAAASec/Oa99GBNkQxQco27v0XwZG4rDcWPQH0buwCNcBGAsYHQ/s1600/ts-04.png)

เราประกาศฟังก์ชันที่ต้องรับ `number` แต่เวลาเรียกใช้งาน ดันใส่ `string` เข้าไป ถ้าเป็น JavaScript จะสามารถนำไปรันได้ (แต่ผลออกมาจะเกิด Runtime-Error หรือมีโลจิคผิดพลาดตรงไหนจะไม่สามารถรู้ได้เลย)

แต่สำหรับ TypeScript นั้นจะแจ้งออกมาตั้งแต่ตอนสั่งคอมไพล์เลยว่าเรามีการเขียนผิดพลาดเกิดขึ้นนะ และ Editor ส่วนใหญ่ก็สามารถแสดงผลข้อผิดพลาดพวกนี้แบบ real time ได้อยู่แล้วด้วย ทำให้ไม่ต้องเสียเวลาพิมพ์ไปซะนาน แต่พอเอามารันเพิ่งจะรู้ว่าพิมพ์ผิดไป





## เริ่มสร้างโปรเจคกันเถอะ


![](https://1.bp.blogspot.com/-JW5T9VTVtrE/XwNGmHBuWUI/AAAAAAAASeE/vlaNoNztwF8-ewXvA_FXiZHqqfg6If5TwCNcBGAsYHQ/s1600/ts-02.png)

ในโปรเจคขนาดใหญ่ แน่นอนว่าเราไม่ได้มีแค่ไฟล์ sourcecode แค่ไฟล์สองไฟล์ แต่อาจจะมีเป็นร้อยๆ ไฟล์ การจะมานั่งคอมไพล์ TypeScript ทีละไฟล์นั้นทำไม่ได้แน่นอน (จริงๆ ก็ทำได้ แต่ใครจะทำนะ !)

การสร้างโปรเจคเลยเป็นทางเลือกที่ดีกว่า เช่นเราอาจจะสร้างโฟลเดอร์ `/src` สำหรับเอาไว้เก็บไฟล์ `.ts` ทั้งหมด และสร้างโฟลเดอร์ `/dist` หรือ distribute เอาไว้เก็บไฟล์ JavaScript

```
.
├── src
│   ├── app1.ts
│   ├── app2.ts
│   ├── app3.ts
│   └── ...
└── dist
    ├── app1.js
    ├── app2.js
    ├── app3.js
    └── ...
```

ซึ่งสำหรับ TypeScript เราสามารถใช้คำสั่งนี้

```
tsc --init
```

เพื่อกำหนดว่าโฟลเดอร์นั้นจะเป็นโปรเจค TypeScript ได้ หลังจากสั่งคำสั่งนั้นแล้ว เราจะได้ไฟล์ `tsconfig.json` มา ภายในไฟล์จะมี config สำหรับโปรเจคอยู่ หน้าตาประมาณนี้

![](https://1.bp.blogspot.com/-nS5sIv3w8nE/XwM861Tx40I/AAAAAAAASds/kuO7AwC5oycilnAC8cqnddf22luEmnqxACNcBGAsYHQ/s1600/ts-01.png)

*รายละเอียดการเซ็ตค่า tsconfig สามารถดูได้ที่เว็บไซต์ของ [TypeScript](https://www.typescriptlang.org/docs/handbook/tsconfig-json.html)*

ปกติการตั้งค่าหลักๆ ก็จะใช้ของที่ TypeScript สร้างมาให้ ยกเว้นบางตัวที่เราต้องเซ็ตเอง เช่น

```json
{
  "compilerOptions": {

    // สั่งให้คอมไพล์ออกมาเป็น JavaScript ES6
    "target": "es6",

    // ชื่อโหลเดอร์ที่ output ไฟล์ JavaScript ที่คอมไพล์แล้ว
    "outDir": "./dist",

    // ชื่อโฟลเดอร์ sourcecode ไฟล์ TypeScript
    "rootDir": "./src",

    // หากใช้งานกับ React คือมีไฟล์ .tsx ให้คอมไพล์เป็น .jsx ด้วย
    "jsx": "react",

    // หากใช้กับ node
    "moduleResolution": "node",
  },

  // กำหนดขอบเขตของไฟล์ที่จะให้คอมไพล์
  // เช่น ทุกไฟล์ที่นามสกุล .ts ในโฟลเดอร์ไหนก็ได้ใต้ /src
  "include": [
      "src/**/*.ts"
  ],

  // กำหนดไฟล์และโฟลเดอร์ที่ไม่ต้องคอมไพล์ 
  // เช่นโฟลเดอร์ node_modules หรือไฟล์ spec
  "exclude": [
      "node_modules",
      "**/*.spec.ts"
  ]
}
```

จากนั้นเวลาเราจะคอมไพล์ ก็สั่งแค่

```
tsc
```

มันก็จะเป็นการคอมไพล์ทุกไฟล์ในทั้งโฟลเดอร์ `/src/` ที่เรากำหนดไว้

แต่นี่ก็เป็นการคอมไพล์แค่ครั้งเดียว ถ้าเรามีการแก้ไขไฟล์ TypeScript เพิ่ม เราก็ต้องสั่งคอมไพล์อีกรอบ ถ้าไม่อยากจะมาสั่งคอมไพล์ทุกรอบที่แก้ไขโค้ด เราสามารถสั่งคอมไพล์แบบ watch ได้

```
tsc --watch
```

คำสั่งนี้จะเป็นการสั่งให้คอมไพล์พร้อมกับ watch หรือคอยดูไฟล์ทุกไฟล์เอาไว้ด้วย ถ้ามีการเปลี่ยนแปลงให้คอมไพล์ใหม่ทันทีโดยเราไม่ต้องสั่งอีกรอบ