รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก
สร้างโครงสร้าง 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 ในทันที — ไม่ต้องติดตั้งในเครื่องของคุณ
บทเรียนทั้งหมดในหลักสูตรนี้
- คุณสมบัติ readonly
- อาร์เรย์และทูเพิล readonly
- ReadonlyArray และ ReadonlyMap
- รูปแบบการทำให้เปลี่ยนแปลงไม่ได้อย่างลึก