Skip to main content

State คืออะไร ?


Basic

ใน JavaScript เราสามารถสร้างตัวแปรไว้ใน Closures Function ได้ เมื่อเรามีการเรียกฟังก์ชัน ค่าตัวแปรก็ยังคงเป็นค่าเดิมอยู่ ไม่ได้ถูก Assign เป็นค่าเริ่มต้นใหม่

const counter = () => {
let count = 0;
return () => {
return ++count;
};
};

const increment = counter();

const result1 = increment();
const result2 = increment();

console.log(result1); // 1
console.log(result2); // 2

สำหรับ React ก็คือหลักการเดียวเลย แต่เราจะสร้างตัวแปรด้วย React Hook ที่ชื่อว่า useState แทน

const MyComponent = () => {
const [count, setCount] = useState<number>(0);

return (
<div>
<div>count: {count}</div>
</div>
);
};

State เป็นตัวแปรพิเศษที่เมื่อค่ามีการเปลี่ยนแปลงเมื่อไหร่ ก็จะส่งผลให้ Component เกิดการ re-render

หากเพื่อน ๆ ลองสร้างตัวแปรด้วยวิธีปกติ แล้วลองเปลี่ยนค่าดู ก็จะพบว่าตัวแปรเปลี่ยนค่าแล้วแต่หน้าเว็บของเรายังคงแสดงผลเหมือนเดิมเลย

const MyComponent = () => {
let count = 0;

count++;

return (
<div>
<div>count: {count}</div> // ไม่ re-render
</div>
);
};

แสดงว่าถ้าเราต้องการสร้างตัวแปรที่มีผลกับการแสดงผลของหน้าเว็บ จะต้องใช้ State เท่านั้นครับ

ใน Function Component เราสามารถสร้าง State ผ่าน useState ที่เป็น 1 ใน Built in Hook ของ React ครับ

การใช้งาน useState จะส่งค่าออกมาเป็น Tuple โดยที่

  1. index ตัวแรกเป็นตัวแปร State
  2. index ที่สองเป็น setter function

ส่วนมากเราจะนิยมตั้งชื่อ เป็น something และ setSomething ครับ เช่น name และ setName เป็นต้น

นอกจากนี้ useState ยังรับ Parameters ได้ 1 ตัว คือค่า Default หากไม่ได้ใส่จะมีค่า Default เป็น undefined ครับ นอกจากนี้ถ้าเราใช้ TypeScript ก็สามารถใส่ Type ของ State ได้ที่ Generic เลยครับ

//               v setter function         v default value
const [count, setCount] = useState<number>(0);
// ^ state ^ generic

ตัวแปร State จะเก็บค่าใดไว้ก็ได้ เหมือนกับตัวแปรปกติของ JavaScript แต่เราต้องเปลี่ยนค่าของ state ผ่าน Setter Function เท่านั้น หากเราเปลี่ยนค่าของ State ตรง ๆ Component จะไม่ re-render ครับ

const [count, setCount] = useState<number>(0);

count += 1; // ❌ ไม่เกิดการ re-render
setCount(10); // ✅

เนื่องจาก useState เป็น React hook ดังนั้น เราจะเรียกใช้ได้ที่ Top Level ของ Component เท่านั้น หมายความว่า ไม่สามารถเรียกได้ใน Loop หรือ Condition ครับ

const MyComponent = () => {
const [count, setCount] = useState<number>(0);

// ❌ เกิด Error
if (count > 10) {
useState(0);
}

return (
<div>
<div>count: {count}</div>
</div>
);
};

export default MyComponent;

เราไปดูรายละเอียดของ state และ setState กันครับ

state

state เหมือนกับตัวแปรตามปกติของ JavaScript คือเราสามารถนำไปใช้เพื่อแสดงผล ui ใช้ในฟังก์ชัน หรือใช้ใน Loop แต่ข้อแตกต่างเดียวก็คือการเปลี่ยนค่า state ตรง ๆ จะไม่ทำให้เกิดการ re-render ตามที่คุยกันก่อนหน้านี้

setState

setState เป็นฟังก์ชันที่ใช้เพื่ออัพเดตค่า state และทำให้เกิดการ rerender สามารถรับ paramerters ได้ 1 ตัว คือค่า state ใหม่ เมื่อเรียกใช้แล้ว ค่า state จะถูกอัพเดตและแสดงผลค่าใหม่ในการ rerender ครั้งถัดไป ขอย้ำว่าในการ rerender ครั้งถัดไป

// ❌ สุดท้ายจะ +1 แค่ครั้งเดียว เพราะยังไม่ได้ re-render ก่อน
const tripleIncrement = () => {
setCount(count + 1);
setCount(count + 1);
setCount(count + 1);
};

แต่ถ้าเพื่อน ๆ ต้องการ setState ที่ต้องมีการอ้างอิงค่า state ปัจจุบัน เช่น เหมือนในตัวอย่าง ผมก็ขอแนะนำการใชัอีกแบบ นั่นคือการใส่ค่า state เป็น Callback Function

ถ้าเราใส่แบบธรรมดา เราต้องนำค่า state เดิมจากการเรียกใช้ useState มาใช้ แต่อย่าลืมนะ ว่า state จะถูกอัพเดตจริง ๆ ในการ rerender ครั้งถัดไป แสดงว่าถึงแม้เราจะเรียก setState ไปแล้วก็จริง แต่ถ้าเราเอา state มาใช้ต่อทันที เราก็จะยังได้ค่าเดิมอยู่ เนื่องจาก component ยังไม่ rerender นั่นเอง และบางครั้งในการเขียนที่ซับซ้อนมากขึ้น เช่น การเขียน Asynchronous หรือ useCallback ก็อาจจะทำให้มีการอัพเดต state ที่ผิดได้อีกถ้าไม่ได้ระวังให้ดี

แต่ถ้าเราใช้ Callback Function เราสามารถเข้าถึงค่าก่อนหน้าของ state ได้เลย และสามารถนำมาใช้ได้อย่างปลอดภัย เพราะการเขียนแบบจะทำให้ React สามารถจัดลำดับการทำงานของ setState ได้อย่างถูกต้อง

// ✅ ทำงานถูกต้อง
const tripleIncrement = () => {
setCount((count) => count + 1);
setCount((count) => count + 1);
setCount((count) => count + 1);
};

Summary

สรุปแล้ว state ก็เหมือนตัวแปรปกติทั่วไปของ JavaScript มีข้อแตกต่างคือต้องอัพเดตค่าผ่าน Setter Function เท่านั้น ถ้าเป็นการอัพเดตค่าแบบง่าย ๆ ก็สามารถใส่ค่าลงใน Setter Function ได้เลย แต่ถ้าต้องการเข้าถึงค่า state ก่อนหน้า หรือเป็นการเขียน Asynchronous ก็ควรใส่ค่าเป็น Callback Function นั่นเอง

หากเพื่อน ๆ คนไหนยังสับสนกับการทำงานของ React ที่ rerender อะไรสับสนวุ่นวายก็ไม่ต้องเป็นห่วง เดี๋ยวในตอนถัดไปเราจะไปดูรายละเอียดของ React Lifecycle กัน