Photo by Rahul Mishra / Unsplash

การเปลี่ยนค่า Object ใน state ของ React.js

React.js Mar 16, 2023

สวัสดีครับ จริงๆ บทความนี้ก็เป็นกึ่งๆ อ่านสรุป + แปลจากต้นฉบับแหละครับ ตัวอย่างโค๊ด และคำอธิบาย ก็อ่านจากเว็บ reactjs และผมก็ไม่ได้เอามาทั้งหมด ฉะนั้นใครต้องการความเข้าใจมากกว่านี้ แนะนำอ่านจากเว็บต้นฉบับดีกว่า เพราะเค้ามีตัวอย่างประกอบ เข้าใจง่ายขึ้น ถ้าได้เห็น ได้ลองแก้โค๊ดไปด้วย


State ใน React.js เราสามารถจะเก็บค่าอะไรก็ได้ โดยปกติทั่วๆไป ก็พวก number string bool รวมถึง Object หรือ Array ใช่มั้ยครับ ซึ่งวันนี้เราจะมาพูดถึงการเก็บ state แบบ Object กัน

ข้อสำคัญในการเก็บ state เป็น Object

  • ควรมอง object ใน state ให้เป็น read-only
  • ถ้าจะอัพเดท state ต้องสร้าง object ใหม่ หรือ copy เอา (จะไม่แก้ไข object เดิม)
  • การแก้ไข object เดิม ตัว React.js จะไม่รู้ว่า state มีการเปลี่ยน และจะไม่ถูก re-render
const [user, setUser] = useState({ id: 1, name: 'John Doe' });

โดยปกติ เราจะไม่ทำแบบนี้

user.id = 2
user.name = 'Jane Doe'

แต่จะใช้วิธี Copy หรือสร้าง object ใหม่แทน แบบนี้

setUser({
  id: 2,
  name: 'Jane Doe'
})

// หรือ copy user object เดิมมาเปลี่ยน แค่ name id เดิม
setUser({
  ...user,
  name: 'John Doe Jr.'
})

เรื่องของ Nested Object

บางครั้ง เราก็เก็บ state เป็น object หลายๆ ชั้น เหมือนกันใช่มั้ย (แต่จริงๆ ไม่ควรเท่าไหร่) แล้วเราจะอัพเดทมันยังไง? ตัวอย่างโค๊ด (จากเว็บ beta.reactjs.org)

const [person, setPerson] = useState({
  name: 'Niki de Saint Phalle',
  artwork: {
    title: 'Blue Nana',
    city: 'Hamburg',
    image: 'https://i.imgur.com/Sd1AgUOm.jpg',
  }
})

สมมติ เราจะอัพเดทค่า พวก artwork.city artwork.title ถ้า Object ปกติเราก็ mutate เปลี่ยนค่ามันตรงๆได้เลย แบบนี้

person.artwork.title = 'New title'

แต่ React เราไม่สามารถไป mutate object ที่มัน render ไปแล้วได้ และ React มันก็ไม่รู้ว่า state มันเปลี่ยน วิธีการให้ React รู้ว่า state เปลี่ยน คือทำผ่าน useState ( setPerson ) เท่านั้น  

ตัวอย่างการใช้ spread operator ( ... ) ในการ Copy Object

setPerson({
  ...person, // 1. copy person object
  artwork: { // 2. artwork เป็น key เหมือนเดิม
    ...person.artwork, // 3. copy artwork
    city: 'New Delhi' // 4. เปลี่ยนแค่ city
  }
});

// หรือมีค่าเท่ากับแบบด้านล่างนี้
const nextArtwork = { ...person.artwork, city: 'New Delhi' };
const nextPerson = { ...person, artwork: nextArtwork };
setPerson(nextPerson);

ข้อควรระวังเรื่อง nest object บางครั้ง มันก็ไม่ใช่การ copy object จริงๆ เนื่องจาก object ใน JavaScript ใช้วิธี reference ตัวอย่าง

let obj1 = {
  title: 'Blue Nana',
  city: 'Hamburg',
  image: 'https://i.imgur.com/Sd1AgUOm.jpg',
};

let obj2 = {
  name: 'Niki de Saint Phalle',
  artwork: obj1
};

let obj3 = {
  name: 'Copycat',
  artwork: obj1
};

ถ้าเราเปลี่ยนค่า obj2.artwork.title จะเห็นว่า obj3 และ obj1 ก็เปลี่ยนด้วย เพราะว่า obj3.artwork มัน refenrece ไปหา obj1 นั่นเอง

จริงๆ เรื่องของ nest object state ถ้าเลี่ยงได้ ก็น่าจะเลี่ยง และจริงๆ ก็ไม่ควรเก็บหลายชั้นมากๆ หรือถ้าเลี่ยงไม่ได้จริงๆ ตาม structure ของที่ออกแบบไว้ ก็ต้องไปดูพวก concept flattening, normalized หรือใช้ library อย่าง immer.js ช่วย (อาจจะไม่ค่อยเหมาะกับมือใหม่)

Introduction to Immer | Immer
Immer (German for: always) is a tiny package that allows you to work with immutable state in a more convenient way.

สรุป

  • ถ้าเรามี 2 state และมักจะอัพเดทพร้อมกัน แนะนำ merge เป็น อันเดียวดีกว่า
  • การเลือก state และการวางโครงสร้าง state ควรคำนึงถึงความง่าย และลดข้อผิดพลาดด้วย เช่น ถ้าเรา nest object หลายๆชั้น หรือแยกกันเยอะเกินไป อะไรพวกนี้
  • ไม่ควร เก็บค่า props ใส่ state นอกจากจะไม่ต้องการให้มัน update
  • พวก UI เช่น select dropdown, options พวก state ที่เราเลือก ควรเก็บเป็น id ดีกว่า object
  • ถ้า nest object มันอ่านยาก และซับซ้อน ให้ flattening มันซะ

Happy Coding ❤️

Reference

Updating Objects in State
The library for web and native user interfaces

Tags

Chai Phonbopit

เป็น Web Dev ทำงานมา 10 ปีหน่อยๆ ด้วยภาษา JavaScript, Node.js, React, Vue และปัจจุบันกำลังสนใจ Web3, Crypto และ Blockchain เขียนบล็อกที่ https://devahoy.com