0Pricing
TypeScript Academy · บทเรียน

รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก

สร้างโครงสร้าง readonly อย่างลึกด้วยชนิดข้อมูลแบบเรียกซ้ำ

รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก เป็นบทเรียน TypeScript Academy ฟรีบน CoddyKit นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน คุณสามารถอ่านบทเรียนทั้งหมดด้านล่างฟรี — จากนั้นลองปฏิบัติด้วยตัวคุณเองในเบราว์เซอร์พร้อมตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7 บทเรียนนี้เป็นส่วนหนึ่งของเส้นทางการเรียน TypeScript Academy และความก้าวหน้าของคุณจะซิงค์ข้ามเว็บและแอป CoddyKit คอร์ส TypeScript Academy มีบทเรียนทั้งหมด 4 บทเรียน

ความไม่เปลี่ยนแปลงแบบตื้นเทียบกับแบบลึก

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

interface State { readonly user: { name: string }; }
const s: State = { user: { name: 'Ada' } };
// s.user = {...}; // Error (shallow protection)
s.user.name = 'Grace'; // allowed! nested field is mutable
console.log(s.user.name);

เหตุใดแบบตื้นจึงไม่เพียงพอ

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

interface Config { readonly db: { host: string; port: number }; }
const c: Config = { db: { host: 'localhost', port: 5432 } };
c.db.port = 9999; // mutates nested data despite readonly
console.log(c.db.port);

ชนิด DeepReadonly แบบเวียนซ้ำ

เราสามารถสร้างชนิดข้อมูลแบบแมปที่ใช้ readonly แบบเวียนซ้ำได้ DeepReadonly<T> จะตรวจสอบทุกคุณสมบัติ และหากคุณสมบัตินั้นเป็นอ็อบเจ็กต์ ก็จะเรียกใช้ตัวเองกับอ็อบเจ็กต์นั้นต่อ

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};
console.log('DeepReadonly defined');

การใช้ DeepReadonly

การห่อชนิดข้อมูลด้วย DeepReadonly จะทำให้คุณสมบัติที่ซ้อนอยู่ไม่สามารถเปลี่ยนแปลงได้เช่นกัน ตอนนี้แม้แต่ s.user.name ก็ไม่สามารถกำหนดค่าใหม่ได้ เพราะการป้องกันครอบคลุมลงไปจนสุด

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};
type State = DeepReadonly<{ user: { name: string } }>;
const s: State = { user: { name: 'Ada' } };
// s.user.name = 'x'; // Error now
console.log(s.user.name);

ชนิดข้อมูลแบบแมปทำงานอย่างไร

[K in keyof T] จะวนซ้ำผ่านคีย์ทุกตัว คำนำหน้า readonly จะล็อกคีย์แต่ละตัว ส่วนเงื่อนไข T[K] extends object ? ... : T[K] จะตัดสินใจว่าจะเรียกซ้ำหรือหยุดที่ชนิดข้อมูลพื้นฐาน

type DeepReadonly<T> = {
  readonly [K in keyof T]: T[K] extends object
    ? DeepReadonly<T[K]>
    : T[K];
};
type Cfg = DeepReadonly<{ a: number; b: { c: string } }>;
const cfg: Cfg = { a: 1, b: { c: 'x' } };
console.log(cfg.a, cfg.b.c);

การป้องกันเฉพาะขณะคอมไพล์

DeepReadonly บังคับใช้ความไม่เปลี่ยนแปลงในระบบชนิดข้อมูล ไม่ใช่ขณะทำงาน อ็อบเจ็กต์ JavaScript ยังคงเปลี่ยนแปลงได้ในทางเทคนิคผ่านเส้นทางที่ไม่มีการระบุชนิดข้อมูล แต่โค้ดที่มีชนิดข้อมูลของคุณได้รับการป้องกันอย่างสมบูรณ์

// Type-level immutability does not freeze the runtime object.
// For runtime guarantees, combine it with Object.freeze.
console.log('Types guard your code; freeze guards runtime');

การแช่แข็งอ็อบเจ็กต์ที่ซ้อนอยู่ขณะทำงาน

สำหรับความไม่เปลี่ยนแปลงจริงขณะทำงาน ให้ใช้ Object.freeze ซึ่งป้องกันการเพิ่ม ลบ หรือเปลี่ยนแปลงคุณสมบัติ แต่เช่นเดียวกับ readonly การทำงานจะเป็นแบบตื้นโดยค่าเริ่มต้น

const config = Object.freeze({ host: 'localhost', port: 8080 });
console.log(config.port);
// In strict mode, config.port = 1 throws; otherwise silently ignored.

ตัวช่วยแช่แข็งแบบลึก

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

function deepFreeze<T>(obj: T): T {
  Object.values(obj as any).forEach((v) => {
    if (v && typeof v === 'object') deepFreeze(v);
  });
  return Object.freeze(obj);
}
const frozen = deepFreeze({ a: { b: 1 } });
console.log(frozen.a.b);

ความไม่เปลี่ยนแปลงเพื่อสถานะที่ปลอดภัยยิ่งขึ้น

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

const state = { count: 0, user: { name: 'Ada' } };
// Immutable update: create a new object instead of mutating
const next = { ...state, count: state.count + 1 };
console.log(state.count, next.count);

รูปแบบการอัปเดตแบบไม่เปลี่ยนแปลงข้อมูล

เมื่อใช้ข้อมูลแบบ readonly คุณจะสร้างเวอร์ชันใหม่ผ่านการกระจายค่า การอัปเดตแต่ละครั้งจะสร้างอ็อบเจ็กต์ใหม่ ดังนั้นสถานะก่อนหน้าจึงยังคงเดิม ซึ่งมีประโยชน์อย่างมากสำหรับการเลิกทำ การแก้จุดบกพร่องแบบย้อนเวลา และการตรวจจับการเปลี่ยนแปลง

type State = { readonly items: readonly string[] };
const s: State = { items: ['a'] };
const updated: State = { items: [...s.items, 'b'] };
console.log(s.items, updated.items);

การรวมความปลอดภัยระดับชนิดข้อมูลและขณะทำงาน

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

function freeze<T>(obj: T): T {
  return Object.freeze(obj);
}
const settings = freeze({ theme: 'dark', version: 2 });
console.log(settings.theme, settings.version);

แบบทดสอบสั้นๆ

ทดสอบความเข้าใจเกี่ยวกับความไม่เปลี่ยนแปลงแบบลึก

สรุป: ความไม่เปลี่ยนแปลงแบบลึก

คุณได้เรียนรู้ว่า:

  • readonly เป็นแบบตื้น ฟิลด์ที่ซ้อนอยู่ยังคงเปลี่ยนแปลงได้
  • ชนิดข้อมูลแบบแมป DeepReadonly<T> ที่เวียนซ้ำจะใช้ readonly กับทุกระดับ
  • มีผลเฉพาะขณะคอมไพล์เท่านั้น หากต้องการบังคับใช้ขณะทำงาน ให้ใช้ Object.freeze แบบลึก
  • ความไม่เปลี่ยนแปลงช่วยให้สถานะปลอดภัยและคาดเดาได้ผ่านรูปแบบการคัดลอกเพื่ออัปเดต

เนื้อหานี้จบเส้นทาง TypeScript Academy ในหัวข้อชนิดข้อมูลขั้นสูงและความไม่เปลี่ยนแปลงแล้ว

type State = { readonly value: number };
const s: State = { value: 1 };
const next: State = { ...s, value: s.value + 1 };
console.log(s.value, next.value);

คำถามที่พบบ่อย

บทเรียน “รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก” ฟรีหรือไม่

ใช่ — ข้อความเต็มของ “รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก” ฟรีให้อ่านที่นี่บนเว็บ เพื่อปฏิบัติแบบโต้ตอบ (ตัวแก้ไขโค้ดในตัวและติวเตอร์ AI ตลอด 24/7) และปลดล็อคส่วนที่เหลือของคอร์ส TypeScript Academy ให้อัปเกรดเป็น CoddyKit PRO คอร์ส TypeScript Academy มีบทเรียนทั้งหมด 4 บทเรียน

คุณจะเรียนรู้อะไรในบทเรียน “รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก”

สร้างโครงสร้าง readonly อย่างลึกด้วยชนิดข้อมูลแบบเรียกซ้ำ คุณปฏิบัติ TypeScript Academy ด้วยโค้ดที่ใช้งานได้จริงที่คุณเรียกใช้โดยตรงในเบราว์เซอร์ และติวเตอร์ AI ตลอด 24/7 ตอบคำถามของคุณขณะที่คุณไปผ่านบทเรียน

คุณต้องมีประสบการณ์ก่อนที่จะเริ่มเรียน TypeScript Academy หรือไม่

ไม่จำเป็นต้องมีประสบการณ์มาก่อน TypeScript Academy บน CoddyKit ออกแบบมาสำหรับผู้เริ่มต้นไปจนถึงผู้เรียนขั้นสูง คุณสามารถเริ่มต้นที่นี่หรือเริ่มจากตัวแรกและเรียนด้วยความเร็วของคุณเอง นี่คือบทเรียนที่ 4 จากทั้งหมด 4 บทเรียน

บทเรียน “รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก” ใช้เวลานานแค่ไหน

บทเรียน CoddyKit ส่วนใหญ่ใช้เวลาประมาณ 5–10 นาที แต่ละบทเรียนจึงสั้นและเป็นแบบโต้ตอบ คุณสามารถก้าวหน้าอย่างต่อเนื่องและกลับมาเรียนต่อจากตรงที่เพิ่งหยุดบนเว็บและแอปได้เลย

ฉันเขียนและรันโค้ดในบทเรียน TypeScript Academy นี้ได้ไหม

ได้ บทเรียน TypeScript Academy ทุกบทมีตัวแก้ไขโค้ดในตัว คุณจึงเขียนและรันโค้ดจริงได้เลยในเบราว์เซอร์ และได้รับข้อเสนอแนะจาก AI ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ

บทเรียนทั้งหมดในหลักสูตรนี้

  1. คุณสมบัติ readonly
  2. อาร์เรย์และทูเพิล readonly
  3. ReadonlyArray และ ReadonlyMap
  4. รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก
← กลับไปที่ TypeScript Academy