ไทย

ปลดล็อกพลังของ TypeScript const assertions เพื่อการอนุมาน Type แบบไม่เปลี่ยนรูป เพิ่มความปลอดภัยและความคาดเดาได้ของโค้ดในโปรเจกต์ของคุณ เรียนรู้วิธีใช้อย่างมีประสิทธิภาพพร้อมตัวอย่าง

TypeScript Const Assertions: การอนุมาน Type แบบไม่เปลี่ยนรูปเพื่อโค้ดที่แข็งแกร่ง

TypeScript ซึ่งเป็น superset ของ JavaScript นำ static typing มาสู่โลกที่ไม่หยุดนิ่งของการพัฒนาเว็บ หนึ่งในฟีเจอร์ที่ทรงพลังคือการอนุมานไทป์ (type inference) ซึ่งคอมไพเลอร์จะสรุปไทป์ของตัวแปรโดยอัตโนมัติ Const assertions ซึ่งเปิดตัวใน TypeScript 3.4 ได้ยกระดับการอนุมานไทป์ไปอีกขั้น ทำให้คุณสามารถบังคับใช้ immutability และสร้างโค้ดที่แข็งแกร่งและคาดเดาได้มากขึ้น

Const Assertions คืออะไร?

Const assertions คือวิธีบอก TypeScript compiler ว่าคุณตั้งใจให้ค่าๆ หนึ่งไม่สามารถเปลี่ยนแปลงได้ (immutable) โดยใช้ синтаксис as const ต่อท้ายค่า literal หรือนิพจน์ ซึ่งจะสั่งให้คอมไพเลอร์อนุมานไทป์ที่แคบที่สุดเท่าที่เป็นไปได้ (literal type) สำหรับนิพจน์นั้น และทำเครื่องหมาย properties ทั้งหมดเป็น readonly

โดยพื้นฐานแล้ว const assertions ให้ระดับความปลอดภัยของไทป์ที่เข้มงวดกว่าการประกาศตัวแปรด้วย const เพียงอย่างเดียว ในขณะที่ const ป้องกันการกำหนดค่าใหม่ให้กับตัวแปรเอง แต่มันไม่ได้ป้องกันการแก้ไข object หรือ array ที่ตัวแปรนั้นอ้างอิงถึง แต่ const assertions จะป้องกันการแก้ไข properties ของ object ด้วย

ประโยชน์ของการใช้ Const Assertions

ตัวอย่างการใช้งานจริง

ตัวอย่างที่ 1: การใช้งานพื้นฐานกับ Literal

หากไม่มี const assertion, TypeScript จะอนุมานไทป์ของ message เป็น string:


const message = "Hello, World!"; // Type: string

เมื่อมี const assertion, TypeScript จะอนุมานไทป์เป็น literal string "Hello, World!":


const message = "Hello, World!" as const; // Type: "Hello, World!"

สิ่งนี้ช่วยให้คุณสามารถใช้ literal string type ในการกำหนดไทป์และการเปรียบเทียบที่แม่นยำยิ่งขึ้น

ตัวอย่างที่ 2: การใช้ Const Assertions กับ Arrays

พิจารณา array ของสี:


const colors = ["red", "green", "blue"]; // Type: string[]

แม้ว่า array จะถูกประกาศด้วย const คุณยังสามารถแก้ไของค์ประกอบของมันได้:


colors[0] = "purple"; // ไม่มีข้อผิดพลาด
console.log(colors); // Output: ["purple", "green", "blue"]

โดยการเพิ่ม const assertion, TypeScript จะอนุมาน array เป็น tuple ของ readonly strings:


const colors = ["red", "green", "blue"] as const; // Type: readonly ["red", "green", "blue"]

ตอนนี้ การพยายามแก้ไข array จะทำให้เกิดข้อผิดพลาดใน TypeScript:


// colors[0] = "purple"; // Error: Index signature in type 'readonly ["red", "green", "blue"]' only permits reading.

สิ่งนี้ช่วยให้แน่ใจว่า colors array จะยังคงไม่สามารถเปลี่ยนแปลงได้

ตัวอย่างที่ 3: การใช้ Const Assertions กับ Objects

เช่นเดียวกับ arrays, objects ก็สามารถทำให้ไม่สามารถเปลี่ยนแปลงได้ด้วย const assertions:


const person = {
  name: "Alice",
  age: 30,
}; // Type: { name: string; age: number; }

แม้จะใช้ const คุณยังสามารถแก้ไข properties ของ person object ได้:


person.age = 31; // ไม่มีข้อผิดพลาด
console.log(person); // Output: { name: "Alice", age: 31 }

การเพิ่ม const assertion ทำให้ properties ของ object เป็น readonly:


const person = {
  name: "Alice",
  age: 30,
} as const; // Type: { readonly name: "Alice"; readonly age: 30; }

ตอนนี้ การพยายามแก้ไข object จะทำให้เกิดข้อผิดพลาดใน TypeScript:


// person.age = 31; // Error: Cannot assign to 'age' because it is a read-only property.

ตัวอย่างที่ 4: การใช้ Const Assertions กับ Objects และ Arrays ที่ซ้อนกัน

Const assertions สามารถนำไปใช้กับ objects และ arrays ที่ซ้อนกันเพื่อสร้างโครงสร้างข้อมูลที่ไม่สามารถเปลี่ยนแปลงได้ในระดับลึก (deeply immutable) พิจารณาตัวอย่างต่อไปนี้:


const config = {
  apiUrl: "https://api.example.com",
  endpoints: {
    users: "/users",
    products: "/products",
  },
  supportedLanguages: ["en", "fr", "de"],
} as const;

// Type:
// {
//   readonly apiUrl: "https://api.example.com";
//   readonly endpoints: {
//     readonly users: "/users";
//     readonly products: "/products";
//   };
//   readonly supportedLanguages: readonly ["en", "fr", "de"];
// }

ในตัวอย่างนี้ config object, endpoints object ที่ซ้อนอยู่ และ supportedLanguages array ทั้งหมดถูกทำเครื่องหมายเป็น readonly สิ่งนี้ช่วยให้แน่ใจว่าไม่มีส่วนใดของ configuration ที่สามารถถูกแก้ไขโดยไม่ได้ตั้งใจในขณะรันไทม์

ตัวอย่างที่ 5: Const Assertions กับ Return Types ของฟังก์ชัน

คุณสามารถใช้ const assertions เพื่อให้แน่ใจว่าฟังก์ชันจะคืนค่าที่ไม่สามารถเปลี่ยนแปลงได้ สิ่งนี้มีประโยชน์อย่างยิ่งเมื่อสร้างฟังก์ชันยูทิลิตี้ที่ไม่ควรแก้ไขข้อมูลอินพุตหรือสร้างเอาต์พุตที่สามารถเปลี่ยนแปลงได้


function createImmutableArray(items: T[]): readonly T[] {
  return [...items] as const;
}

const numbers = [1, 2, 3];
const immutableNumbers = createImmutableArray(numbers);

// Type of immutableNumbers: readonly [1, 2, 3]

// immutableNumbers[0] = 4; // Error: Index signature in type 'readonly [1, 2, 3]' only permits reading.

กรณีการใช้งานและสถานการณ์ต่างๆ

การจัดการ Configuration

Const assertions เหมาะอย่างยิ่งสำหรับการจัดการ configuration ของแอปพลิเคชัน โดยการประกาศ configuration objects ของคุณด้วย as const คุณสามารถมั่นใจได้ว่า configuration จะยังคงสอดคล้องกันตลอดวงจรชีวิตของแอปพลิเคชัน ซึ่งจะช่วยป้องกันการแก้ไขโดยไม่ได้ตั้งใจที่อาจนำไปสู่พฤติกรรมที่ไม่คาดคิด


const appConfig = {
  appName: "My Application",
  version: "1.0.0",
  apiEndpoint: "https://api.example.com",
} as const;

การกำหนดค่าคงที่

Const assertions ยังมีประโยชน์สำหรับการกำหนดค่าคงที่ด้วย literal types ที่เฉพาะเจาะจง ซึ่งสามารถปรับปรุงความปลอดภัยของไทป์และความชัดเจนของโค้ดได้


const HTTP_STATUS_OK = 200 as const; // Type: 200
const HTTP_STATUS_NOT_FOUND = 404 as const; // Type: 404

การทำงานกับ Redux หรือ State Management Libraries อื่นๆ

ใน state management libraries เช่น Redux, immutability เป็นหลักการสำคัญ Const assertions สามารถช่วยบังคับใช้ immutability ใน reducers และ action creators ของคุณ ป้องกันการเปลี่ยนแปลง state โดยไม่ได้ตั้งใจ


// ตัวอย่าง Redux reducer

interface State {
  readonly count: number;
}

const initialState: State = { count: 0 } as const;

function reducer(state: State = initialState, action: { type: string }): State {
  switch (action.type) {
    default:
      return state;
  }
}

การรองรับหลายภาษา (Internationalization - i18n)

เมื่อทำงานกับการรองรับหลายภาษา คุณมักจะมีชุดของภาษาที่รองรับและรหัส locale ที่สอดคล้องกัน Const assertions สามารถช่วยให้แน่ใจว่าชุดข้อมูลนี้จะไม่สามารถเปลี่ยนแปลงได้ ป้องกันการเพิ่มหรือแก้ไขโดยไม่ได้ตั้งใจที่อาจทำให้การใช้งาน i18n ของคุณเสียหาย ตัวอย่างเช่น สมมติว่ารองรับภาษาอังกฤษ (en), ฝรั่งเศส (fr), เยอรมัน (de), สเปน (es) และญี่ปุ่น (ja):


const supportedLanguages = ["en", "fr", "de", "es", "ja"] as const;

type SupportedLanguage = typeof supportedLanguages[number]; // Type: "en" | "fr" | "de" | "es" | "ja"

function greet(language: SupportedLanguage) {
  switch (language) {
    case "en":
      return "Hello!";
    case "fr":
      return "Bonjour!";
    case "de":
      return "Guten Tag!";
    case "es":
      return "¡Hola!";
    case "ja":
      return "こんにちは!";
    default:
      return "Greeting not available for this language.";
  }
}

ข้อจำกัดและข้อควรพิจารณา

ทางเลือกอื่นนอกเหนือจาก Const Assertions

แม้ว่า const assertions จะเป็นเครื่องมือที่ทรงพลังในการบังคับใช้ immutability แต่ก็มีแนวทางอื่นที่คุณสามารถพิจารณาได้:

แนวทางปฏิบัติที่ดีที่สุด

สรุป

TypeScript const assertions เป็นเครื่องมือที่มีค่าสำหรับการบังคับใช้ immutability และปรับปรุงความปลอดภัยของไทป์ในโค้ดของคุณ โดยการใช้ as const คุณสามารถสั่งให้คอมไพเลอร์อนุมานไทป์ที่แคบที่สุดเท่าที่เป็นไปได้สำหรับค่า และทำเครื่องหมาย properties ทั้งหมดเป็น readonly สิ่งนี้สามารถช่วยป้องกันการแก้ไขโดยไม่ได้ตั้งใจ ปรับปรุงความคาดเดาได้ของโค้ด และปลดล็อกการตรวจสอบไทป์ที่แม่นยำยิ่งขึ้น แม้ว่า const assertions จะมีข้อจำกัดบางประการ แต่ก็เป็นการเพิ่มที่ทรงพลังให้กับภาษา TypeScript และสามารถเพิ่มความแข็งแกร่งของแอปพลิเคชันของคุณได้อย่างมาก

ด้วยการนำ const assertions ไปใช้อย่างมีกลยุทธ์ในโปรเจกต์ TypeScript ของคุณ คุณสามารถเขียนโค้ดที่น่าเชื่อถือ บำรุงรักษาง่าย และคาดเดาได้มากขึ้น โอบรับพลังของการอนุมานไทป์แบบ immutable และยกระดับแนวทางการพัฒนาซอฟต์แวร์ของคุณ