จาก JavaScript ไปยัง TypeScript เริ่มยังไง
Tags: typescript, javascript
Published: 2021-03-11 16:29:10


โดยปกติผมจะเขียนโค้ดอยู่คนเดียว ความ dynamic type ของ JavaScript ก็ไม่ได้สร้างปัญหา สร้าง bug ให้กับผม แต่เนื่องจาก ต้องการจะลองใช้งาน [Nestjs](https://nestjs.com/) ซึเขียนด้วย TypeScript จึงบันทึกเอาไว้สักหน่อยว่าจะเปลี่ยนจาก JavaScript ไปยัง TypeScript นั้นต้องทำยังไงบ้าง

## Prerequisites
1. มีความเข้าใจภาษา JavaScript ระดับหนึ่ง
2. ตั้งค่าโปรเจคเพิ่มเขียน TypeScript จาก [ที่นี่](https://dev.to/somprasongd/typescript-node-js-jb7)

## TypeScript คืออะไร 🤔

ซึ่งถ้าถามว่า TypeScript คืออะไร ผมคงตอบไปสั้นๆ ว่า คือการเอา static type มาใส่เข้าไปใน JavaScript นั่นเอง
ลองดูตัวอย่างนี้ เช่น มี function สำหรับเพิ่มค่าตัวเลข ถ้าเป็น Javascript เราจำเป็นที่จะต้องตรวจสอบ type ก่อน เพื่อป้องกันการเกิด error

```js
// app.js
let counter = 10;

function increateCounter() {
  if (typeof counter === 'number') {
    counter += 1;
  } else {
    throw new Error('counter is not a number');
  }
}
```

แต่ถ้าใช้ TypeScript เราไม่ต้องตรวจสอบ type เองแล้ว

```ts
let counter: number = 10;

function increateCounter() {
  counter += 1;
}
```

ชีวิตดูดีขึ้นเยอะ 👍

## มาทำความรู้จัก Type กันก่อน 😏

มาลองดูกันว่า TypeScript มีชนิดข้อมูลแบบไหนบ้าง
### 1. Predefined Types

เป็นชนิดของข้อมูลที่ถูกเตรียมไว้ให้แล้ว โดยขอเริ่มจของ JavaScript ซึ่งที่ใช้งานบ่อยๆ นั้นก็จะมีประมาณนี้

- String
- Number
- Boolean
- Array
- Function
- Object

และส่วนที่เพิ่มเข้ามาจาก TypeScript มีดังนี้

- Any - ถ้าไม่รู้จะใส่ type อะไร ให้ใส่ตัวนี้ไปเลย และมันก็จะไปปิดการทำ type checking
- Tuple - คือ Array ที่ต้องระบุขนาดตายตัว และต้องกำหนดกำหนดชนิดข้อมูลให้แต่ละ elements ด้วย เช่น

```ts
let rgbColor = [number, number, number];
```

- Enum - ใช้กำหนดชื่อให้กับค่าชุดหนึ่งได้ โดยค่าจะเริ่มจาก 0 เช่น

```ts
enum ArrowKeys {
  Up, // 0
  Down, // 1
  Left, // 2
  Right, // 3
}
// หรือจะกำหนดค่าเองก็ได้
enum ArrowKeys {
  Up = 'up',
  Down = 'down',
  Left = 'left',
  Right = 'right',
}
```

> ยังมีอีกหลาย type แต่เอาเท่าที่ใช้งานบ่อยๆ ก่อน

### วิธีกำหนดชนิดข้อมูลให้กับตัวแปร

ิวิธีกำหนดนั้นง่ายมาก เพียงแค่ใส่ `:type` หลังชื่อตัวแปรก็ได้แล้ว

- เริ่มจากชนิดข้อมูลพื้นฐานก่อน String, Number และ Boolean

```js
// javascript
let name = 'Stamp';
let age = 5;
let isSterilized = true;
```

เมื่อเขียนแบบ TypeScript

```ts
// typescript
let name: string = 'Stamp';
let age: number = 5;
let isSterilized: boolean = true;
```

- Array บอกไว้ก่อนไม่ได้ใช้ `:array` นะ 😆

```js
// javascript
let favoriteFoods = ['Chicken', 'Corn', 'Strawberry'];
```

เมื่อเขียนแบบ TypeScript ให้ดูว่าข้อมูลใน array เป็นอะไร ก็ใช้ type แล้วตาม `[]`

```ts
// typescript
let favoriteFoods: string[] = ['Chicken', 'Corn', 'Strawberry'];
```

- Function จะมีกำหนดอยู่ 2 ส่วน 
คือ 1. ตรง parameter และ 2. ค่าที่ต้อง return กลับไป

```js
// javascript
let generateFullName = (firstName, lastName) => {
  return firstName + ' ' + lastName;
};
```

เมื่อเขียนแบบ TypeScript

```ts
// typescript
let generateFullName = (firstName: string, lastName: string): string => {
  return firstName + ' ' + lastName;
};
```

- Object

```js
// javascript
let dog = {
  name: 'Stamp',
  age: 5,
  gender: 'male',
  isSterilized: true,
  favoriteFoods: ['Chicken', 'Corn', 'Strawberry'],
};
```

เมื่อเขียนแบบ TypeScript ก็กำหนดลงไปตรงๆ หลังชื่อตัวแปรได้เลย (ใช้ `;` นะ)

```ts
// typescript
let dog: {
  name: string;
  age: number;
  gender: string;
  isSterilized: boolean;
  favoriteFoods: string[];
} = {
  name: 'Stamp',
  age: 5,
  gender: 'male',
  isSterilized: true,
  favoriteFoods: ['Chicken', 'Corn', 'Strawberry'],
};
```

### 2.Custom Type

จากการกำหนด type ของ object แบบข้างบนนั้น ถ้าเรามี dog2 ก็ต้องมากำหนด type แบบนี้ซ้ำขึ้น ทำให้ขาดความ reuseable ไปดังนั้นเราจำเป็นที่จะต้องสร้าง custom type ขึ้นมาเอง โดยการใช้ `type` และ `interface`

#### แล้ว type คืออะไร 🤨

`type` นั้นเป็นการสร้าง alias type ขึ้นมาเพื่อบอกว่า type นั้นๆ ควรมีหน้าตาเป็นยังไง เช่น `gender` นั้นสามารถระบุได้แค่ `male` กับ `female` เท่านั้น เราก็สามารถสร้างเป็น `genderType` ขึ้นมาได้เพื่อให้ระบุได้เฉพาะ สองค่านี้เท่านั้น ทำได้โดย

```ts
type genderType = 'male';

let gender: genderType = 'male';
```

แต่ถ้าลองกำหนด `let gender: genderType = 'female';` แบบนี้จะยังทำไม่ได้ เราจะต้องใช้ Union Operater มาช่วยในการสร้าง type ให้สมบูรณ์

```ts
type genderType = 'male' | 'female';

let gender: genderType = 'female';
```

#### แล้ว interface มีไว้ทำไม😅

เจ้า interface นี่คือเอาไว้กำหนดหว่า Object นั้นๆ จะมีหน้าตาเป็นยังไง 

โดยจะเอาตัวอย่างข้างบน มาสร้าง interface ดู

```ts
// typescript
interface Dog {
  name: string;
  age: number;
  gender: string;
  isSterilized: boolean;
  favoriteFoods: string[];
}

let dog: Dog = {
  name: 'Stamp',
  age: 5,
  gender: 'male',
  isSterilized: true,
  favoriteFoods: ['Chicken', 'Corn', 'Strawberry'],
};
```

และเรายังสามารถใช้ type และ interface ร่วมกันได้ แบบนี้

```ts
// typescript
type genderType = 'male' | 'female';

interface Dog {
  name: string;
  age: number;
  gender: genderType;
  isSterilized: boolean;
  favoriteFoods: string[];
}

let dog: Dog = {
  name: 'Stamp',
  age: 5,
  gender: 'male',
  isSterilized: true,
  favoriteFoods: ['Chicken', 'Corn', 'Strawberry'],
};
```
คร่าวนี้ก็วามารถ reuse การสร้าง Object ของ dog ได้แล้ว 🎉

### 3. Type Assertion

ในบางกรณีเราอาจสร้าง object เปล่าแบบนี้ `let dog = {};` ซึ่ง TypeScript จะไม่รู้จักว่า Object นี่คือ type อะไร มี property อะไรบ้าง

```ts
let dog = {};
dog.name = 'Snow'; // Property 'name' does not exist on type '{}'.
```

ทำให้เราไม่สามารถกำหนดค่า property ให้กับ dog ได้ เพราะ TypeScript ไม่รู้จัก

แต่สามารถแก้ได้โดยการใช้ `as` ดังนี้

```ts
let dog = {} as Dog; // ใช้ as เพื่อบอกชนิดของ object
dog.name = 'Snow';
dog.age = 5;
dog.gender = 'female';
dog.isSterilized = true;
dog.favoriteFoods = ['Chicken', 'Corn', 'Strawberry'];
```

แบบนี้ TypeScript ก็รู้แล้วว่าเป็น `Dog`

### 4. Generic Type

มาถึงตัวสุดท้ายของบทความนี้แล้ว

งั้นขออธิบายจากโค้ดเลยละกัน

ตัวอย่างเช่น มีฟังก์ชัน `createNumberList` และ `createStringList`

```ts
function createNumberList(item: number): number[] {
  const newList: number[] = [];

  newList.push(item);

  return newList;
}

function createStringList(item: string): string[] {
  const newList: string[] = [];

  newList.push(item);

  return newList;
}

const numberList = createNumberList(123);
const stringList = createNumberList('abc');
```

จะเห็นว่าทั้ง 2 ฟังก์ชันนั้นทำงานเหมือนกันเลย แต่ต่างกันแค่ชนิดของข้อมูล เราจะทำยังไงให้มัน reuseable ได้ โดยให้เหลือแค่ฟังก์ชัน createList เท่านั้น

ในการทำแบบนี้เราจะใช้ Generic Type เข้ามาช่วย แบบนี้

```ts
function createList<CustomType>(item: CustomType): CustomType[] {
  const newList: CustomType[] = [];

  newList.push(item);

  return newList;
}

const numberList = createList<number>(123);
const stringList = createList<string>('abc');
```

ตรงคำว่า `CustomType` นั่นด็คือ Generic จริงๆ แล้วจะใช้ชื่ออะไรก็ได้ แต่โดยทั่วไปจะใช้ตัว `T` กัน แบบนี้

```ts
function createList<T>(item: T): T[] {
  const newList: T[] = [];

  newList.push(item);

  return newList;
}

const numberList = createList<number>(123);
const stringList = createList<string>('abc');
```

ซึ่งจะเห็นว่าการใช้งาน Generic Type นั้นจะช่วยให้โค้ดของเราสามารถ reuseable ได้แล้ว

---

จริงๆ แล้ว TypeScript นั้นยังมีเนื้อหาอีกเยอะ แต่คิดว่าเมื่อเข้าใจเรื่อง Type ในบทความนี้ดีแล้ว ก็น่าจะเริ่มเขียน TypeScript ได้แล้วหล่ะครับ ^^