ข้อกำหนด ES2023 ได้เพิ่มเมธอดของ array ใหม่สี่ตัว ได้แก่ toSorted, toReversed, toSpliced และ with ซึ่งจะคืนค่าเป็น array ใหม่แทนที่จะเป็นการแก้ไขข้อมูลใน array เดิม (mutate) สำหรับ React และไลบรารี UI อื่นๆ ที่พึ่งพา immutable state เมธอดเหล่านี้จะช่วยให้นักพัฒนาสามารถแทนที่เทคนิคการใช้ spread operator ที่มักจะเป็นต้นเหตุของบั๊กและทำให้โค้ดดูเยิ่นเย้อมาอย่างยาวนาน

ทำไมการเปลี่ยนแปลงนี้จึงสำคัญ

React ตัดสินใจว่าจะเรนเดอร์คอมโพเนนต์ใหม่หรือไม่ โดยการเปรียบเทียบ reference ของ state ก่อนหน้ากับ state ใหม่ หาก reference ไม่เปลี่ยนแปลง React จะถือว่าไม่มีอะไรเปลี่ยนแปลง เมธอด Array.prototype.sort แบบดั้งเดิมจะทำการเรียงลำดับข้อมูลใน array เดิม (in place) และคืนค่า reference เดิมกลับมา ดังนั้นการเรียกใช้เช่น setTasks(prev => prev.sort(fn)) จะทำให้ React มองไม่เห็นการอัปเดต ส่งผลให้ UI ค้างอยู่ที่ข้อมูลที่ล้าสมัย ซึ่งเป็นบั๊กที่พบได้บ่อยมากในโปรเจกต์จริง

นักพัฒนาเคยแก้ปัญหานี้ด้วยการคัดลอก (clone) array ก่อน โดยมักจะใช้ spread operator เพื่อให้ขั้นตอนการเรียงลำดับสร้าง reference ใหม่ขึ้นมา:

setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

รูปแบบนั้นใช้งานได้ แต่ทำให้โค้ดดูรกและอาจลืมทำได้ง่าย เมธอดใหม่ใน ES2023 จึงเป็นวิธีที่ตรงไปตรงมาและอ่านง่ายในการสร้าง array ใหม่โดยที่ยังคงรักษา array ต้นฉบับไว้โดยไม่มีการเปลี่ยนแปลง

เมธอดทั้งสี่แบบไม่เปลี่ยนแปลงข้อมูลต้นฉบับ (non-mutating)

  • toSorted(compareFn?) – ทำงานเหมือน sort แต่คืนค่าเป็นสำเนาที่เรียงลำดับแล้ว โดยไม่มีการเปลี่ยนแปลง array ต้นฉบับ
  • toReversed() – มาแทนที่ reverse โดยจะคืนค่าเป็นสำเนาที่เรียงลำดับย้อนกลับ และคงลำดับเดิมของ array ต้นฉบับไว้
  • toSpliced(start, deleteCount, ...items) – ทำงานเหมือน splice แต่ไม่มี side effects โดย array ที่คืนค่ามาจะสะท้อนการเพิ่มหรือลบข้อมูล แต่ array ต้นฉบับจะยังคงเดิม
  • with(index, value) – แทนที่สมาชิกที่ตำแหน่ง index ด้วย value และคืนค่าเป็น array ใหม่ ซึ่งมาแทนที่รูปแบบการใช้ map หรือการใช้ spread operator เพื่อเปลี่ยนค่าสมาชิกที่พบบ่อย

ทั้งสี่เมธอดเป็นส่วนหนึ่งของมาตรฐาน ECMAScript และพร้อมใช้งานในเบราว์เซอร์เวอร์ชันปัจจุบันและ Node.js 20

หน้าตาของโค้ดในปัจจุบัน

การเรียงลำดับรายการ (Sorting a list)

// Before
setTasks(prev => [...prev].sort((a, b) => b.priority - a.priority));

// After
setTasks(prev => prev.toSorted((a, b) => b.priority - a.priority));

การอัปเดตรายการเดียว (Updating a single item)

// Before
setItems(prev =>
  prev.map((item, i) => (i === idx ? newItem : item))
);

// After
setItems(prev => prev.with(idx, newItem));

การเรียงลำดับย้อนกลับ (Reversing an array)

setLogs(prev => prev.toReversed());

การลบสมาชิก (Removing an element)

setTags(prev => prev.toSpliced(removeIdx, 1));

ไวยากรณ์ใหม่นี้ช่วยลดความจำเป็นในการใช้ spread operator เพิ่มเติมหรือการใช้ loop แบบ mapping ทำให้การอัปเดต state อ่านง่ายขึ้นและเกิดข้อผิดพลาดน้อยลง

ใครได้ประโยชน์และใครที่อาจต้องลังเล

นักพัฒนา ที่ใช้ React, Vue, Redux, Zustand หรือเฟรมเวิร์กใดๆ ที่ต้องการโครงสร้างข้อมูลแบบ immutable จะมีโมเดลความคิด (mental model) ที่ชัดเจนขึ้น: เพียงเรียกใช้เมธอด ได้ array ใหม่ แล้วส่งกลับไปที่ setter การลดโค้ดส่วนเกิน (boilerplate) ยังอาจช่วยลดเวลาในรอบการเรนเดอร์ลงได้ไม่กี่มิลลิวินาที เนื่องจาก engine ไม่ต้องสร้างสำเนาชั่วคราวก่อนการเรียงลำดับ

ทีมที่ต้องรองรับเบราว์เซอร์รุ่นเก่า อาจจำเป็นต้องใช้ polyfills เนื่องจากเมธอดเหล่านี้ยังไม่มีใน Safari หรือ Internet Explorer เวอร์ชันเก่า ดังนั้นการ build สำหรับ production ที่ต้องรองรับแพลตฟอร์มเหล่านั้นจะต้องรวม fallback เข้าไปด้วย ซึ่งจะเพิ่มขนาด bundle เล็กน้อย แต่เมื่อเทียบกับความง่ายในการอ่านโค้ดแล้วถือว่าคุ้มค่า

ผู้สร้างไลบรารี อาจจำเป็นต้องอัปเดต type definitions (เช่น TypeScript) เพื่อเปิดให้ใช้งาน signature ใหม่ๆ ได้ จนกว่าการกำหนดประเภทเหล่านี้จะถูกบรรจุลงในแพ็กเกจ @types อย่างเป็นทางการ นักพัฒนาอาจพบข้อผิดพลาดด้าน type ชั่วคราว

สิ่งที่ควรจับตามองต่อไป

  • ตัวชี้วัดการใช้งาน (Adoption metrics) – เครื่องมืออย่าง ESLint อาจมีการเพิ่มกฎที่แจ้งเตือนเมื่อมีการเรียกใช้เมธอด array แบบ mutable ใน state setter เพื่อผลักดันให้นักพัฒนาเปลี่ยนไปใช้เมธอดใหม่
  • การศึกษาด้านประสิทธิภาพ (Performance studies) – ผลการทดสอบเบื้องต้นบ่งชี้ว่าเมธอดแบบ non-mutating ที่เป็น native นั้นเร็วกว่าการใช้ spread operator เพื่อ clone แล้วตามด้วยการทำงานแบบ mutable แต่ข้อมูลจากการใช้งานจริงจะช่วยยืนยันผลกระทบนี้ได้ชัดเจนกว่า
  • ข้อเสนอเพิ่มเติม (Further proposals) – คณะกรรมการ ECMAScript ยังคงสำรวจการสร้าง API ที่เป็น immutable โดยค่าเริ่มต้น (immutable-by-default) การติดตามขั้นตอนการพัฒนาที่กำลังจะมาถึงอาจทำให้เราพบเมธอดช่วยเหลืออื่นๆ ที่ใช้รูปแบบเดียวกันนี้

บทสรุป

ข้อกำหนด ECMAScript ล่าสุดช่วยให้นักพัฒนา UI มีวิธีที่สั้นกระชับและมีมาให้ในตัว เพื่อรักษา state ให้เป็นแบบ immutable โดยไม่ต้องใช้เทคนิคการใช้ spread operator ที่ซับซ้อนซึ่งเคยทำให้เกิดบั๊กนับไม่ถ้วน การเปลี่ยนจาก sort, reverse, splice และการแทนที่ด้วย index มาเป็น toSorted, toReversed, toSpliced และ with จะช่วยให้ระบบตรวจจับการเปลี่ยนแปลงของ React ทำงานได้อย่างที่ตั้งใจและทำให้โค้ดของคุณอ่านง่ายขึ้น หากเบราว์เซอร์เป้าหมายของคุณรองรับเมธอดใหม่เหล่านี้ หรือคุณยินดีที่จะใช้ polyfill ก็ถึงเวลาแล้วที่จะเลิกใช้รูปแบบเดิมๆ และปล่อยให้ภาษา JavaScript จัดการงานหนักแทนคุณ