มีอะไรใหม่บ้างใน TypeScript 4.4

Nuttavut Thongjor

TypeScript 4.4 beta ได้ถูกปล่อยออกมาเรียบร้อยแล้ว มาดูกันซิว่ามีฟีเจอร์ใหม่อะไรให้ใช้งานบ้าง

กำหนดพรอพเพอร์ตี้ด้วย Symbol และ Template String ใน Index Signatures

ก่อนหน้านี้การกำหนดชนิดข้อมูลให้กับ Index Signatures ส่วนของพร็อพเพอร์ตี้ต้องมีชนิดข้อมูลเป็น string หรือ number เท่านั้น เช่น

TypeScript
1interface Options {
2 // property มี key เป็น string
3 [key: string]: unknown
4}
5
6const options: Options = { writeable: true }

TypeScript 4.4 อนุญาตให้เราสามารถใช้ Template Literal เพื่อกำหนดรูปแบบข้อความให้ปรากฎเป็นค่า key ของพร็อพเพอร์ตี้ได้ เช่นกรณีที่เราต้องการให้พร็อกเพอร์ตี้เป็นข้อความที่ขึ้นต้นด้วยคำว่า data- เราสามารถกำหนด key ด้วย data-${string} ที่มีนัยยะว่าพร็อพเพอร์ตี้นั้นต้องขึ้นต้นด้วยคำว่า data- และลงท้ายด้วย string ใด ๆ ก็ได้

TypeScript
1interface DataAttrs {
2 [key: `data-${string}`]: string;
3}
4
5const linkAttrs: DataAttrs = {
6 'data-testid': 'my-link',
7 'data-anchor': 'my-link',
8 'data-color': '#ee66ee'
9}

นอกจากนี้ TypeScript 4.4 ยังอนุญาตให้ใช้ชนิดข้อมูล Symbol เป็นพรอพเพอร์ตี้ได้เช่นกัน

TypeScript
1interface SpecialTypes<T> {
2 [sym: symbol]: (value: T) => void
3}
4
5const types: SpecialTypes<string> = {
6 [Symbol('ITERATION')]: (item) => {
7 /* ... */
8 },
9 [Symbol('INSERTION')]: (item) => {
10 /* ... */
11 },
12}

เมื่อ TypeScript 4.4 มาถึง Index Signatures จึงอนุญาตให้เราใช้ชนิดข้อมูลได้ถึงสี่ประเภทคือ string, number, symbol และ template string โดยแต่ละชนิดข้อมูลยังสามารถเชื่อมต่อกันผ่านชนิดข้อมูล union (|) ได้อีกด้วย

TypeScript
1interface KeyPair {
2 [key: string | symbol]: unknown
3}

Exact Optional Property Types

ออบเจ็กต์บน JavaScript หากเข้าถึงพรอพเพอร์ตี้ที่ไม่มีจริงเราย่อมได้ค่า undefined กลับออกมา ผลลัพธ์ดังกล่าวเป็นเช่นเดียวกับกรณีที่เข้าถึงพรอพเพอร์ตี้ที่มีค่าเป็น undefined

Code
1const person = {
2 name: 'Somchai',
3 age: 24,
4 tel: undefined,
5}
6
7console.log(person.tel) // undefined
8console.log(person.address) // undefined

เมื่อเป็นเช่นนี้ TypeScript จึงถือว่ากรณีของการใช้ Optional Property ก็ควรอนุญาตให้กำหนด undefined ให้กับมันได้ด้วย พิจารณาพรอพเพอร์ตี้ address ที่เป็น Optional Property ต่อไปนี้

TypeScript
1interface Person {
2 name: string
3 age: number
4 tel: string | undefined
5 address?: string
6}

TypeScript จะถือว่าพรอพเพอร์ตี้ดังกล่าวมีชนิดข้อมูลเดียวกันกับโค้ดข้างล่างที่อนุญาตให้กำหนด undefined ให้กับมันได้โดยตรง

TypeScript
1interface Person {
2 name: string
3 age: number
4 tel: string | undefined
5 address?: string | undefined
6}

กำหนดให้มีตัวแปร person ที่มีชนิดข้อมูลเป็น Person ดังต่อไปนี้

TypeScript
1const person: Person = {
2 name: 'Somchai',
3 age: 24,
4 tel: undefined,
5}

กรณีของโค้ดข้างต้นเรากล่าวได้ว่าทั้ง person.tel และ person.address ต่างได้ค่าเป็น undefined

Code
1console.log(person.tel) // undefined
2console.log(person.address) // undefined

แม้เราจะระบุ tel ให้มีค่าเป็น undefined และละเว้นค่า address เอาไว้ แต่ในโค้ดของเราก็ยังยากที่จะทราบอยู่ดี ว่า tel และ address ได้ค่าเป็น undefined เนื่องจากไม่มีพรอพเพอร์ตี้นี้ในออบเจ็กต์หรือ เป็นเพราะมีพรอพเพอร์ตี้แต่ค่าของมันเป็น undefined กันแน่ เราจึงต้องพึ่งการดำเนินการบางอย่างเช่นการใช้ in ในการตรวจสอบแทน

Code
1'name' in person // true
2'address' in person // false
3'tel' in person // true

เพื่อให้จำแนกได้อย่างชัดเจน TypeScript จึงไม่ควรกำหนดให้ Optional Property สามารถกำหนด undefined ให้กับมันได้โดยตรง กล่าวคือชนิดข้อมูล Person ข้างต้นนี้เมื่อกำหนดพรอพเพอร์ตี้ address เป็น address?: string ย่อมไม่ควรให้มีความหมายเดียวกับ address?: string | undefined นั่นเอง

TypeScript 4.4 สามารถระบุตัวเลือกขอคอมไพเลอร์ด้วย flag --exactOptionalPropertyTypes ค่านี้เป็นการแจ้ง TypeScript ให้ทราบว่าอย่าเติม | undefined ให้กับ Optional Property

TypeScript
1interface Person {
2 name: string
3 age: number
4 tel: string | undefined
5 address?: string
6}
7
8const person: Person = {
9 name: 'Somchai',
10 age: 24,
11 tel: undefined,
12}
13
14// กรณีไม่ได้เปิดใช้ --exactOptionalPropertyTypes
15// ชนิดข้อมูลเป็น string | undefined
16person.address
17
18// สามารถทำได้
19person.address = undefined
20
21// กรณีไม่ได้เปิดใช้ --exactOptionalPropertyTypes
22// ชนิดข้อมูลเป็น string เท่านั้น
23person.address
24
25// ไม่สามารถทำได้
26person.address = undefined

การกำหนดชนิดข้อมูล unknown สำหรับตัวแปรใน catch ของประโยค try/catch

ส่วนของบลอค try ในภาษา JavaScript สามารถโยนข้อผิดพลาดด้วยชนิดข้อมูลใด ๆ ก็ได้ เช่น Error หรือ TypeError ด้วยเหตุนี้ตัวแปรสำหรับการรับค่าใน catch TypeScript จึงกำหนดชนิดข้อมูลให้เป็น any

TypeScript
1try {
2 throw new Error('Error Na Ja')
3 // หรือ
4 throw new TypeError('Error Ei Ei')
5
6 // เพื่อให้ ex สามารถรองรับการ throw ที่ต่างชนิดข้อมูลกันได้
7 // ex จึงมีชนิดข้อมูลเป็น any
8} catch (ex) {}

เป็นที่ทราบกันดีว่าการใช้ any นั้นเอื้อให้เกิดข้อผิดพลาดได้ง่าย เนื่องจาก any จะไม่ถูกตรวจสอบชนิดข้อมูลเมื่อคอมไพล์

TypeScript
1try {
2 throw new Error('Error Na Ja')
3 // หรือ
4 throw new TypeError('Error Ei Ei')
5} catch (ex) {
6 // เรียกใช้ได้ TypeScript ไม่ตรวจสอบเนื่องจาก ex เป็น any
7 console.log(ex.eiei)
8 console.log(ex.naja)
9}

TypeScript 4.4 มีแฟลคใหม่ชื่อ --useUnknownInCatchVariables ที่เมื่อเปิดใช้แล้วจะทำให้ชนิดข้อมูลของตัวแปร ใน catch เป็น unknown กรณีที่เปิดแฟลค --strict ก็จะรวมการทำงานของ --useUnknownInCatchVariables เข้าไว้แล้วด้วยเช่นกัน

TypeScript
1// กำหนดให้เปิดใช้ --useUnknownInCatchVariables
2// หรือ --strict
3try {
4 throw new Error('Error Na Ja')
5} catch (ex) {
6 // Property 'eiei' does not exist on type 'unknown'.
7 // ไม่สามารถเรียก eiei ได้
8 console.log(ex.eiei)
9
10 // ทำงานได้อย่างถูกต้อง
11 if (ex instanceof Error) {
12 console.error(ex.message)
13 }
14}

สำหรับภาษา TypeScript type guard คือกระบวนการตรวจสอบชนิดข้อมูลเพื่อจำกัดขอบเขตของชนิดข้อมูลนั้นให้แคบลง โดยอาศัยประโยคเงื่อนไข เช่น if ในการทำงาน

TypeScript
1function squeeze<T extends string | number>(item: T) {
2 // type guard ตรวจสอบ word ก่อนว่าเป็น string หรือไม่
3 if (typeof item === 'string') {
4 // TypeScript จะทราบว่า item เป็น string
5 // จึงสามารถใช้เมธอด replace ของ string ได้
6 return item.replace(/[_\t\n ]/g, '')
7 }
8
9 return `${item}`.split('').reduce((acc, ch) => +acc + +ch, 0)
10}

กรณีที่เราแยกเงื่อนไขการตรวจสอบออกนอกประโยคเงื่อนไข if TypeScript จะไม่สามารถจำแนกได้อีกต่อไปว่า item มีชนิดข้อมูล เป็น string ไม่ใช่ T

TypeScript
1function squeeze<T extends string | number>(item: T) {
2 const isString = typeof item === 'string'
3
4 if (isString) {
5 // Property 'replace' does not exist on type 'T'
6 return item.replace(/[_\t\n ]/g, '')
7 }
8
9 return `${item}`.split('').reduce((acc, ch) => +acc + +ch, 0)
10}

สำหรับ TypeScript 4.4 หากตัวแปรสำหรับการทดสอบนั้นเป็นค่าคงที่ (const), readonly หรือเป็นการประกาศที่ไม่อนุญาตให้ตัวแปรเปลี่ยนแปลงค่าได้ TypeScript จะสามารถอนุมานการทำงานของ type guard นั้นได้อย่างถูกต้อง

TypeScript
1// สำหรับ TypeScript 4.4
2function squeeze<T extends string | number>(item: T) {
3 // เนื่องจากตัวแปรเป็น const
4 const isString = typeof item === 'string'
5
6 // type guard ทำงานได้ถูกต้อง
7 if (isString) {
8 return item.replace(/[_\t\n ]/g, '')
9 }
10
11 return `${item}`.split('').reduce((acc, ch) => +acc + +ch, 0)
12}

หากเปลี่ยนตัวแปร isString ของเราเป็น let ที่อนุญาตให้แก้ไขค่าข้อมูลภายหลังได้ TypeScript จะไม่ทราบว่าก่อนหน้า type guard ตัวแปรจะถูกเปลี่ยนค่าเป็นอย่างอื่นอีกหรือไม่ การทำงานจึงไม่สมบูรณ์

TypeScript
1// สำหรับ TypeScript 4.4
2function squeeze<T extends string | number>(item: T) {
3 // เนื่องจากตัวแปรเป็น let อาจถูกแก้ไขค่าภายหลังได้
4 let isString = typeof item === 'string'
5
6 // type guard ทำงานได้ไม่ถูกต้อง
7 if (isString) {
8 // ยังคงมองเห็น item เป็นชนิดข้อมูล T
9 // Property 'replace' does not exist on type 'string | number'.
10 return item.replace(/[_\t\n ]/g, '')
11 }
12
13 return `${item}`.split('').reduce((acc, ch) => +acc + +ch, 0)
14}

การปรับปรุงประสิทธิภาพและการเปลี่ยนแปลง

TypeScript 4.4 ยังมีการปรับปรุงประสิทธิภาพการทำงานอีกมาก รวมถึง Breaking Changes บางส่วนด้วย สามารถอ่านรายละเอียด เพิ่มเติมได้จาก Announcing TypeScript 4.4 Beta

เรียนรู้ TypeScript อย่างมืออาชีพ

คอร์สออนไลน์ Comprehensive TypeScript คอร์สสอนการใช้งาน TypeScript ตั้งแต่เริ่มต้นจนถึงขั้นสูง เรียนรู้หลักการทำงานของ TypeScript การประกาศชนิดข้อมูลต่าง ๆ พร้อมการใช้งานขั้นสูงพร้อมรองรับการทำงานกับ TypeScript เวอร์ชัน 4.4 ด้วย

สารบัญ

สารบัญ

  • กำหนดพรอพเพอร์ตี้ด้วย Symbol และ Template String ใน Index Signatures
  • Exact Optional Property Types
  • การกำหนดชนิดข้อมูล unknown สำหรับตัวแปรใน catch ของประโยค try/catch
  • การปรับปรุงประสิทธิภาพและการเปลี่ยนแปลง
  • เรียนรู้ TypeScript อย่างมืออาชีพ