หลังจากที่มีเวลามาลองเล่น React Hooks (มีในเวอร์ชัน 16.6.x ขึ้นไป) โดยระหว่างที่ลองเล่นไป ก็รับรู้ได้ว่า React Hooks จะเน้นไปที่ Function Components มากกว่า Class Components
เพราะถ้าใครเคยลองเขียน React Hooks แล้ว ก็พอจะรู้ได้ทันที ว่ามันใช้ได้เฉพาะการเขียนในรูปแบบของ Function Components เท่านั้น (เรายังเขียนส่วนอื่นเป็นแบบ Class Components ได้อยู่ แค่จะใช้ React Hooks ไม่ได้ ถ้า Components ไหนจะใช้ ต้องเขียนในรูปแบบ Function Component เท่านั่นเอ๊งงงงงงง)
สิ่งที่มาพร้อมกับ React Hook แบบเกาะแขนกันมาเลย คือ useState ก็ถูกสร้างขึ้นมาเพื่อทดแทนการใช้ Local State (this.state) เดิมใน constructor ที่ใช้ได้แค่ในการเขียนแบบ Class Components เท่านั้น
ส่วนที่ useState เพาเวอร์ฟูลกว่า คือ สามารถใช้งานได้ทั้ง 2 แบบ (Class Components และ Function Components) และ ยังสามารถเรียกใช้งานผ่าน Components อื่นได้อีกด้วย
ซึ่งมันไม่มีปัญหาอะไร เพราะ React สามารถเขียนได้ทั้ง Class Components และ Function Components ได้ แต่ว่าการที่ในตัวโปรเจคมีการเขียนโค๊ดที่มีรูปแบบไม่เหมือนกัน มันออกจะไม่มี Standard ซักเท่าไหร่ มันจึงเป็นเหตุผลที่ว่าทำไมถึงควรเขียนเป็น Function Components ทั้งหมดเลย :)
Table of Contents
บทความนี้เป็นการแนะนำการเขียน React ในรูปแบบของ Function Component มีเนื้อหาค่อนข้างยาว เพราะฉนั้นเลือกอ่านตามความขยันนะ
- Basic Function Component
- Props
- Arrow Function Component
- Stateless Function Component
- State
- Event Handler
- Callback Function
- Lifecycle
- Pure React Function Component
- Ref
- PropTypes
Basic Function Component
มาเริ่มจากตัวอย่างการเขียน Function Components แบบง่ายๆ กันก่อน ซึ่งโค๊ดด้านล่างนี้ สามารถอธิบายโครงสร้างของ Function Component แบบง่ายๆ รวดเดียวจบ ดูแล้ว คงไม่ยากจนเกินไปแน่นอน
ทีนี้หากเราต้องการนำเอา Components อื่น มาใช้งาน ก็สามารถทำได้ โดยการเรียกใช้งาน Components นั้นๆ ในรูปแบบของ HTML Element ของ JSX
จบการอธิบาย Basic Function Components มีแค่นี้ เห็นได้ว่าการใช้งาน Function Components ไม่ยากอย่างที่คิด
Props
อธิบายเกี่ยวกับ Props กันก่อน Props ใน React จะใช้ในการส่งข้อมูลระหว่าง Component กับ Component โดยการส่งค่าไปยังอีก Component นึง เราสามารถทำได้โดยการระบุค่าเหล่านั้นผ่านทาง HTML Attributes ของ Component ที่จะส่งไป (เราสามารถตั้งชื่อ Attributes ได้) โดยเราสามารถส่งได้ทั้งที่เป็นข้อมูล และ Function ผ่าน Props ได้
สังเกตได้ว่า เรารับค่าของ Props ผ่านทางการรับค่า Parameters ของ Function Component นั้น โดย Props เป็นตัวแปรประเภท Object การอ้างถึงข้อมูลตอนใช้งานจึงเป็นรูปแบบของการระบุ Key ใน Props Object (ตัวอย่างด้านบนบรรทัดที่ 10)
ใน Javascript มีวิธีการรับค่าของ Object Parameter อีกแบบนึงที่เรียกว่า Object destructuring เพื่อให้เราสามารถระบุ และเรียกใช้งาน Parameters ต่างๆ ได้เลย
วิธีใช้งาน เป็นการระบุ Parameters ต่างๆ ไว้ใน ({ }) ของ Function นั้นๆfunction Headline({ value1, value2 }) { ... }
Arrow Function Component
สำหรับใครที่เขียน Javascript ตั้งแต่ ES6 ขึ้นไป จะคุ้นเคยการเขียน Function ในรูปแบบของ Arrow Function ใน React ก็สามารถเขียนในรูปแบบของ Arrow Function ได้เช่นกัน
Stateless Function Component
Stateless Component ใน React เป็นการสร้าง Component อีกรูปแบบหนึ่ง ที่ภายใน Component จะไม่มีการสร้าง State ขึ้นมา เพราะในบางครั้ง Component ที่เราสร้างขึ้นมาใช้งาน ไม่จําเป็นจะต้องใช้งาน State มันอาจจะเป็นเพียง Component ที่รับข้อมูลเข้ามาแล้วแสดงผลเพียงอย่างเดียว
เมื่อไม่มีการสร้าง State ขึ้นมา จึงไม่จําเป็นต้องสร้าง Component แบบเดิม ที่จะต้อง Extends React.Component ทําให้เวลาที่เราใช้งานก็จะมี Life Cycle ต่างๆ ของ React ติดมาด้วย
วิธีการสร้าง Stateless Component ง่ายๆ คือ การสร้างในรูปแบบ Function ขึ้นมานั้นเอง
State
การมาของ React Hooks ทำให้เราสามารถเรียกใช้งาน state ใน Function Components ได้ จากเดิมที่ถ้าต้องการใช้งาน state จะต้อง Convert จาก Function Component ไปเป็น Class Component
แต่ตั้งแต่ React 16.6.X ขึ้นไป เราสามารถใช้งาน state ใน Function Component ได้โดยการใช้ useState ใน React Hooks นั่นเอง
useState จะทำหน้าที่ในการเก็บข้อมูลเบื้องต้น (Initial State) เอาไว้ แล้ว return กลับมาในรูปแบบของ Array ซึ่งแบ่งออกเป็น 2 ส่วน คือ ส่วนที่เก็บข้อมูล และฟังก์ชันที่ใช้ในการกำหนดค่าใหม่ให้ข้อมูลนั้น ถ้ายังไม่เข้าใจให้ดูตัวอย่างต่อไปนี้ (บรรทัดที่ 8)
เริ่มต้นจากการเรียกใช้งาน useState และกำหนดค่าเริ่มต้นให้แก่มัน ซึ่ง useState จะ return ค่ากลับมาเป็น Array อย่างที่ได้บอกก่อนหน้านี้ เราจึงต้องกำหนดค่าใน Array 2 ตัว ได้แก่ greeting ที่ทำหน้าที่รับข้อมูลจาก useState และ setGreeting ที่เป็นฟังก์ชันสำหรับกำหนดข้อมูลใหม่ลงไปให้ตัวแปร greeting
มาถึงตรงนี้ น่าจะเริ่มจับทางการใช้งาน useState ได้แล้ว และอาจเริ่มเอะใจแล้วว่า ถ้าเรามีค่าที่ต้องเก็บหลายตัว เราต้องสร้างมันแยกออกมาเป็นของแต่ละตัวใช่ไหม ขอตอบว่า “ใช่ครับ”
ถ้าในหน้านั้นมีการเก็บข้อมูลลง state หลายตัว การใช้วิธีนี้ทำให้เราต้องประกาศตัวแปรเยอะตามไปด้วย ซึ่งถ้าเราตัวแปรหลายๆ ตัว ก็สามารถเก็บเป็น Object ได้
แต่ว่า ข้อเสีย คือ
- ทำให้การอ่านโค๊ดยากขึ้นอีกหน่อย
- เวลากำหนดค่าใหม่ให้กับตัวแปรแค่ตัวเดียวใน Object จะต้องส่งค่าอื่นๆ ใน Object นั้นไปด้วย ไม่อย่างนั้นตัวแปรอื่นๆ ก็จะหายไป (ระวังหน่อยนะ)
Event Handler
จากตัวอย่างก่อนหน้านี้ เรามีการใช้งาน onChange Event ใน Input field เพื่อเปลี่ยนแปลงค่าของ greeting การเขียนแบบนั้นทำให้โค๊ด HTML ของเราค่อนข้างที่จะรก และก็อ่านยาก เพราะฉนั้นเราจึงต้องแยกมันออกมา จากโค๊ดส่วนนั้น เพื่อให้ง่ายต่อการจัดการมัน (บรรทัดที่ 12)
ส่วนนี้สำหรับคนที่ใช้ React อยู่แล้ว คงคุ้นเคยกันอยู่แล้ว แค่เปลี่ยนวิธีการประกาศมันเล็กน้อย โดยการคุณสามารถไปดู React event handlers ต่างๆ ได้ใน Doc ของ React ได้เลย
Callback Function
การทำงานต่างที่ Child Component จะไม่สามารถส่งค่า Props กลับมายัง Parent Component ได้ ถ้าหากเราต้องการที่จะส่งค่าบางอย่างกลับมายัง Parent Component จะต้องใช้วิธีการส่งเป็นฟังก์ชันผ่าน Props ไปยัง Child Component เพื่อเรียกใช้งานฟังก์ชันนั้น และส่งข้อมูลกลับมาอีกทีนึง
วิธีการนี้เราสามารถส่งฟังก์ชันไปยัง Child Components อื่นๆ ที่อยู่ในระดับเดียวกันได้ (Sibling Component) ด้วย
Override Component Function with React
เราสามารถทำการ Override Component Function ที่ส่งมาได้ เผื่อในกรณีที่ Parent Component ลืมส่งฟังก์ชันนั้นมา โดยเราจะกำหนดฟังก์ชันขึ้นมาใหม่ เพื่อให้เป็น Default value โดยการ set Default Props ให้กับมัน
Lifecycle
ถ้าใครเคยเขียน React ที่เป็นแบบ Class Component มาก่อน อาจคุ้นเคยการใช้ Lifecycle methods มาบ้าง เช่น componentDidMount, componentWillUnmount และ shouldComponentUpdate ในการเขียนแบบ Function Components นี่ มีทั้งข่าวดีและข่าวร้ายมาบอก
ข่าวร้าย คือ ใน React Function Components มันไม่มีให้ใช้งาน
ข่าวดี คือ ยังมีวิธีอื่น ที่ใช้งานแทนมันได้อยู่
ที่นี้เรามาดูกันว่า อะไรที่ใช้งานไม่ได้ใน React Function Components และเราจะใช้อะไรแทนสิ่งเหล่านั้น
Constructor ในนี้ ไม่มีนะจ๊ะ
เริ่มต้นที่อันแรกเลย Constructor ที่เราใช้ในการกำหนด initial state กัน ไม่มีใน Function Components ซึ่งใน Function Components การกำหนดค่า initial state จะถูกกำหนดไว้ที่ useState นั่นเอง
Mount
ต่อมาก็ Mounting Lifecycle ถ้าคุณต้องการ execute บางอย่างในการ Render ครั้งแรกปกติใน Class Components เราจะใช้ componentDidMount แต่ใน Function Component เราจะใช้ Effect Hook (useEffect) แทน
จากตัวอย่างด้านบน เมื่อคุณรีเฟรช Browser จะสังเกตได้ว่าการ count จะมีการเปลี่ยน จาก 0 เป็น 1 โดยการ Render ครั้งแรกจะแสดง count เป็น 0 ตาม initial state ที่กำหนดไว้ แต่หลังจากนั้นค่า count จะถูกเปลี่ยนเป็น 1 จากการใช้งาน Effect Hook (บรรทัดที่ 12) ซึ่งมันจะอยู่ใน Action เดียวกับ component did mount นั่นเอง
คำสั่ง useEffect ในบรรทัดที่ 12 จะสังเกตว่า Argument ตัวที่ 2 จะเป็น Array ว่างอยู่ ซึ่ง Array ตรงนี้เป็นการกำหนดว่า Effect Hook ตัวนั้นจะเป็น component load (mount) และ component unload (unmount)
ถ้าหากตัวไหนที่ไม่ได้ระบุใน Array นั้น จะทำงานแค่ครั้งแรกครั้งเดียว แต่ถ้าหากระบุ state ลงใน Array มันจะทำงานต่อเนื่องไปเรื่อยๆ (infinite loop)
เหตุผลที่เป็นอย่างนั้นเพราะว่า Effect Hook จะถูกเรียกทุกครั้งที่ state มีการเปลี่ยนแปลง จึงทำให้มันถูกรันไปเรื่อยๆ นั้นเอง
Update
ทุกครั้งที่ Props หรือ State มีการเปลี่ยนแปลงข้อมูล จะส่งผลให้ Component นั้นทำการ re-render ตัวมันเอง เพื่อนำเอาข้อมูลล่าสุด ที่ได้จาก Props หรือ State มาแสดงผล
ตัวอย่างด้านบน เป็นการ Debug ให้เห็นว่า ทุกครั้งที่ State มีการเปลี่ยนข้อมูล จะมีการ re-render ใหม่ทุกครั้ง
ทีนี้ ถ้าเราต้องการที่จะทำอะไรสักอย่างหลังจากที่เกิดการ re-render แล้ว เราสามารถใช้ Effect Hook ได้เลย ซึ่งมันจะอยู่ใน Action เดียวกับ component did update ใน Class Components Lifecyle นั่นเอง
ตัวอย่างด้านบน เราต้องการที่จะเก็บข้อมูลจำนวนครั้งที่นับลง Local storage เพื่อให้ค่ายังคงอยู่แม้ว่าจะถูกรีเฟรช Browser ก็ตาม
การเขียนคำสั่ง Effect Hook จากตัวอย่างด้านบน ทุกครั้งที่มีการ re-renders ทุก State จะโดนเรียกใช้งานทั้งหมดด้วยเช่นกัน แม้ว่าจะไม่เกี่ยวข้องก็ตาม ดังนั้นถ้าหาเราต้องการที่จะให้มีการทำงานเฉพาะเมื่อ count มีการเปลี่ยนแปลงข้อมูลเพียงอันเดียว เราสามารถทำได้โดยการระบุ count ลงไปใน Array (บรรทัดที่ 15)
import React, { useState, useEffect } from 'react';
const App = () => {
const initialCount = +localStorage.getItem('storageCount') || 0;
const [count, setCount] = useState(initialCount);
const handleIncrement = () =>
setCount(currentCount => currentCount + 1);
const handleDecrement = () =>
setCount(currentCount => currentCount - 1);
useEffect(() => {
localStorage.setItem('storageCount', count)
}, [count]);
return (
<div>
<h1>{count}</h1>
<button type="button" onClick={handleIncrement}>
Increment
</button>
<button type="button" onClick={handleDecrement}>
Decrement
</button>
</div>
);
};
export default App;
การใช้งาน Effect Hook โดยเฉพาะการระบุค่าลงไปใน ตัวแปรที่ 2 (ที่เป็น Array) นั้น ค่อนข้างที่จะมีเงื่อนไขที่ทำให้สับสนพอสมควร ถ้าหากยังไม่คล่อง ก็อาจจะทำผิดได้อย่างง่ายดาย (แรกๆ อาจมี infinite loop กันบ้าง 555+)
เพราะฉนั้นจึงสรุปเงื่อนไง ที่ทำให้ Effect Hook ทำงานไว้ให้ดังนี้:
- ถ้าไม่มีการใส่ Argument เลย จะทำงานทุกครั้ง เมื่อข้อมูลมีการเปลี่ยนแปลงของ Component นั้น
- ถ้าใส่เป็น Array ว่าง ([]) จะเป็นการทำงานเฉพาะตอน mount และ unmount
- ถ้าใส่ state ลงไปใน Argument ([count]) มันจะทำงาน ก็ต่อเมื่อค่าที่ระบุลงไปนั้นมีการเปลี่ยนแปลง (อัพเดท)
ส่วน React Component Force Update ปกติจะไม่ค่อยใช้งานกัน เลยไม่เอามาใส่ไว้ในบทความนี้ หากใครต้องการใช้ สารมารถเข้าไปดู Doc ของ React ได้เลย (จิ้มเบาๆ forceUpdate)
Pure React Function Component
โดยทั่วไปแล้ว React ที่จะตรวจสอบและตัดสินใจว่ามี Component ไหนบ้างที่ต้อง re-render โดยการตรวจสอบค่า state และ props ในตัว Component นั้น ว่ามีการเปลี่ยนแปลงหรือไม่ หากไม่มีการเปลี่ยนแปลง จะไม่มีการ re-render เกิดขึ้น แต่ถ้าหากมีการเปลี่ยนแปลง React จะทำการ re-render Component นั้น ซึ่งทำให้ Child Component ที่อยู่ในนั้น ถูก re-render ใหม่ด้วย แม้ว่าจะไม่มีข้อมูลที่เปลี่ยนแปลงเลยก็ตาม
จากตัวอย่างด้านบน เราจะพบว่าทุกครั้งที่เราพิมพ์ลงไปใน Input ค่าใน State (greeting) ก็จะเปลี่ยน เมื่อ React เจอว่ามีค่า State ที่เปลี่ยนไป มันจะทำการ re-render ใหม่ทั้งหมด ซึ่งรวมถึง Count Component ที่ไม่มีการเปลี่ยนแปลงค่าใดๆ เลย
ถ้าโปรเจคเล็กๆ การ re-render ทั้งหมด ก็คงไม่มีปัญหาอะไร แต่ถ้าโปรเจคที่ทำอยู่มีขนาดใหญ่ มีหลาย Components การที่ต้อง re-renders ใหม่ทั้งหมด มันจะทำให้ช้าและเกิดคอขวดขึ้น วิธีแก้ปัญหานี้ คือ การใช้ PureComponent หรือ shouldComponentUpdate เพื่อหลีกเลี่ยงปัญหาคอขาดในการ re-renders ที่จะเกิดขึ้น
ดังนั้น ถ้าหากเราต้องการให้มีการ re-renders เฉพาะ Component ที่ State หรือ Props มีการเปลี่ยนแปลง เราสามารถใช้ React memo ได้ ซึ่งมันเป็นหนึ่งในหัวข้อ React Top-Level API สามารถเข้าไปอ่านเพิ่มเติมกันได้
จากตัวอย่างการใช้งาน React memo โดยเราต้องการให้ Count Component ทำการ re-render ก็ต่อเมื่อมีข้อมูลอัพเดตเกิดขึ้นมาเท่านั้น เราจึงเอาฟังก์ชัน memo เข้ามาครอบ Count Component ไว้
ทีนี้ ตราบใดที่ค่า State หรือ Props ใน Count Component ไม่เปลี่ยน ส่วนนี้ก็จะไม่ถูก re-render อีกต่อไป
Ref
การใช้งาน React Ref จัดว่าเป็นการใช้งานที่อยู่ในกลุ่ม Rare cases พอสมควร เพราะมันเป็นการเข้าถึง และ จัดการข้อมูล ใน DOM นั้นๆ เช่น Focus element, Animations การใช่งานร่วมกับ DOM libraries ของ Third-party ต่างๆ
การใช้งาน Ref ใน Function Component สามารถใช้งานได้โดยการใช้ createRef เช่น ในตัวอย่างนี้ เป็นการใช้ Ref เพื่อให้ cursor ไป Focus ใน Input element
ผมขอ อธิบาย Ref แบบคร่าวๆ หากใครต้องการศึกษาวิธีการใช้งาน Ref ในวิธีต่างๆ สามารถศึกษาการทำงานเพิ่มเติมได้ใน Doc ของ React (Ref and the DOM)
PropTypes
การใช้งาน PropTypes จะเหมือนกันกับใน Class Components เลย มันไม่แตกต่างกัน ซึ่งหลายๆ คนใช้กันเป็นอยู่แล้ว จึงยกตัวอย่าง การใช้งาน PropTypes มาให้ดู
ทั้งหมดนี้ เป็นพื้นฐานสำหรับการเขียน React Function Components ซึ่งอาจจะไม่ได้ลงลึกในบ้างหัวข้อ หรืออาจจะยัง งงๆ อยู่ในบางหัวข้อ ถ้ามีเวลาจะเขียนลงรายละเอียดให้เพิ่มเติมสำหรับในบางหัวข้อ
ในบทความต่อไปดู วิธีการ Fetch Data ใน React Hooks กัน
ขอบคุณที่เข้ามาอ่านกันนะครับ :)