สวัสดีครับ ในบล็อกนี้จะมาแนะนำวิธีการสร้างฟังก์ชัน Hook หรือที่เรียกว่า Custom hook function เพื่อเอาไว้ใช้ใน React Component กัน
จาก React เวอร์ชัน 16.8 เป็นต้นมา เราสามารถใช้ฟังก์ชัน hook ต่างๆ ของทาง React ที่ได้ทำเตรียมไว้ให้ เพื่อใช้ในงานต่างๆ เช่น
- useState ใช้เรียกใช้งานและจัดการค่า state ของ component
- useEffect ใช้ในการดำเนินการต่างๆ ก่อน component จะแสดงผลที่หน้าจอ
- useMemo ใช้จดจำค่าการดำเนินการต่างๆ ที่ซ้ำๆ เดิม จะได้ไม่ต้องคำนวนค่านั้นใหม่หลายรอบ
- useCallback ใช้ในการเรียกใช้งานฟังก์ชัน (การทำงานจะคล้ายกับ useMemo)
- useRef ใช้ในการอ้างอิง Element ใน Html หรือ อ้างอิงฟังก์ชัน
และฟังก์ชันอื่นๆ อีกที่ไม่ได้ยกตัวอย่างมา เราจะสังเกตได้ว่า วิธีการเรียกใช้ฟังก์ชันต่างๆ เหล่านี้จะอำนวยความสะดวกให้กับเรา โดยที่เราไม่จำเป็นต้องไปจัดการขั้นตอนภายในของ React ใดๆ เลย เราก็จะได้ผลลัพธ์มาดำเนินงานของเราต่อแล้ว และฟังก์ชันพวกนี้ที่เรียกว่าฟังก์ชัน Hook ใน React จะนำหน้าชื่อฟังก์ชันด้วยคำว่า use เสมอ
ข้อดีของการสร้างฟังก์ชัน Hook คือ ลดการทำงานที่ซ้ำซ้อน ไม่ต้องมาเขียนโค้ดแบบเดิมๆ ซ้ำกันในหลาย component และสามารถเรียกใช้งานได้ง่าย
ในตัวอย่างของบล็อกนี้ จะเป็นการเรียกข้อมูลจาก API ซึ่ง ถ้าเราเขียน React ก็จะใช้งานการเรียกข้อมูลจาก API เป็นประจำอยู่แล้ว บางคนก็ใช้ฟังก์ชัน fetch บางคนก็จะใช้ไลบรารี่อย่าง axios หรือ node-fetch หรือ react-query เป็นต้น และจะเปรียบเทียบความแตกต่างระหว่างโค้ดแบบที่เขียนปกติ กับ เขียนฟังก์ชัน Hook เอาไว้ใช้เองด้วย
ข้อมูลตัวอย่างจะดึงมากจาก URL : https://jsonplaceholder.typicode.com/posts
รายละเอียดของ Component ในตัวอย่าง
- PostCard – ข้อมูลของ Post จะแสดงหัวข้อกับคำอธิบาย
- PostCardList – แสดงรายการ Post ทุกรายการ โดยจะดึงข้อมูลจาก API มาก่อนแล้วจึงแสดงรายการ Post เมื่อโหลดข้อมูลเสร็จแล้ว
1.การเขียนโค้ดแบบปกติที่ใช้เรียกข้อมูลจาก API
เราจะเรียก API จะเรียกใน Component ที่ใช้แสดงรายการ ในตัวอย่างนี้คือ PostCardList โดยเลือกใช้การดึงข้อมูลด้วยฟังก์ชัน fetch (fetch เป็นฟังก์ชันพื้นฐานของ Javascript เราไม่ต้องลงไลบรารี่ใดๆ เพิ่มเติม)
import { useEffect, useState } from "react";
import PostCard from "./PostCard";
const url = "https://jsonplaceholder.typicode.com/posts";
const PostCardList = () => {
const [data, setData] = useState(null);
const loadData = async () => {
const result = await fetch(url);
const json = await result.json();
setData(json);
};
useEffect(() => {
loadData();
}, []);
return (
<div>
<h3>List of Posts</h3>
{data && data.map((post, index) => <PostCard data={post} key={index} />)}
</div>
);
};
export default PostCardList;
โดยตัวอย่างโค้ดด้านบนนี้จะ ทำการโหลดข้อมูลในฟังก์ชัน loadData ที่ถูกเรียกครั้งแรกเมื่อ Component นั้นถูกโหลดขึ้นมา และเมื่อโหลดข้อมูลเสร็จแล้วจะเก็บค่าใส่ state ที่ชื่อ data เอาไว้
จะเห็นได้ว่าสิ่งที่โค้ดการโหลดข้อมูลนี้ขาดไปคือ การแสดงข้อความขณะโหลดข้อมูล และการแสดงข้อความเมื่อโหลดข้อมูลไม่สำเร็จ ดังนั้นจำเป็นต้องมีตัวแปร state อีก 2 ตัวมาจัดการ state การโหลดข้อมูลและเก็บค่า error ดังนี้
import { useEffect, useState } from "react";
import PostCard from "./PostCard";
const url = "https://jsonplaceholder.typicode.com/posts";
const PostCardList = () => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState(null);
const loadData = async () => {
try {
setLoading(true);
setErrors(null);
setData(null);
const result = await fetch(url);
const json = await result.json();
setData(json);
} catch (err) {
setErrors(err.toString());
} finally {
setLoading(false);
}
};
useEffect(() => {
loadData();
}, []);
if (loading) {
return <p>loading...</p>;
}
if (errors) {
return <p style={{ color: "red" }}>{errors}</p>;
}
return (
<div>
<h3>List of Posts</h3>
{data && data.map((post, index) => <PostCard data={post} key={index} />)}
</div>
);
};
export default PostCardList;
จะเห็นว่าถ้าเราเขียนโค้ดการโหลดข้อมูลจาก API แบบเก็บ state สถานะการโหลดข้อมูล และ error ด้วย จะทำให้ต้องเขียนโค้ดเพิ่มขึ้นมีเยอะเลย
โค้ดโปรเจค บน codesandbox
ให้ลองคิดเล่นๆ ดู ถ้าในแอปของเรามี Component ที่จะต้องใช้การโหลดข้อมูลจาก API แบบนี้อยู่หลาย Component ดังนั้น ทุก Component จะมีการเขียนโค้ดการโหลดแบบในตัวอย่างซ้ำๆ กัน ไปทุก Component จะต่างกันก็แค่ url
ดังนั้น เพื่อให้ง่ายต่อการพัฒนาและแก้ไขแอปของเรา เราจึงเขียนเฉพาะส่วนการโหลดข้อมูลจาก API เอาไว้เป็นฟังก์ชัน Hook ซึ่งในฟังก์ชันที่จะเขียนขึ้นนี้ จะรับค่า url และ ส่งค่า ข้อมูลที่โหลดเสร็จแล้ว สถานะการโหลดข้อมูล และ Error กลับ
2.เขียนฟังก์ชัน Hook ในการโหลดข้อมูล แยกออกมาจาก Component
สร้างโฟล์เดอร์ hooks มาเก็บฟังก์ชันที่จะสร้างมาก่อน จากนั้นสร้างไฟล์ useFetch.js ซึ่งจะเป็นฟังก์ชัน hook ที่เราจะใช้ในการโหลดข้อมูลจาก API ดังนี้
ในไฟล์ useFetch.js เราจะนำโค้ดการโหลดข้อมูลจาก PostCardList มาใช้ และปรับแก้ไขนิดหน่อยเพื่อให้สามารถใช้กับฟังก์ชัน useFetch ได้ ประกอบด้วย
- parameter ได้แก่ url ที่ต้องการจะโหลดข้อมูล
- output จะเป็น [data, loading, errors]
import { useEffect, useState, useRef } from "react";
const useFatch = (url) => {
const [data, setData] = useState(null);
const [loading, setLoading] = useState(false);
const [errors, setErrors] = useState(null);
const loadData = async () => {
try {
setLoading(true);
setErrors(null);
setData(null);
const result = await fetch(url);
const json = await result.json();
setData(json);
} catch (err) {
setErrors(err.toString());
} finally {
setLoading(false);
}
};
const ref = useRef(null);
ref.current = () => {
loadData();
};
useEffect(() => {
ref.current();
}, []);
return [ data, loading, errors ];
};
export default useFatch;
วิธีการเรียกใช้งาน ฟังก์ชัน useFetch เราจะนำไปใช้ที่ PostCardList ดังนี้
const [data, loading, errors] = useFetch(url);
จะเห็นว่า เราจะลดการเขียนโค้ดใน PostCardList ลงได้เยอะมาก และการเรียกใช้งานก็ง่าย ได้ข้อมูลตามที่ต้องการทั้ง data สถานะการโหลดข้อมูล (loading) และ error ดังนั้นหากจะนำฟังก์ชัน useFetch ไปใช้ที่ Component อื่นที่ต้องการโหลดข้อมูลจาก API ก็เพียงแค่ เปลี่ยน url ปลายทางเท่านั้นเอง
เมื่อเราปรับโค้ดให้มีการแยกฟังก์ชัน hook ออกมาจากตัว Component แล้วจะได้โค้ดตามนี้
สรุปส่งท้าย
จากตัวอย่างในบล็อกนี้เป็นเพียงแค่ตัวอย่างในการปรับโค้ดการโหลดข้อมูลจาก API ให้เปลี่ยนเป็นฟังก์ชัน hook เพื่อจะได้นำไปใช้ประโยชน์ใน Component อื่นด้วย และเป็นการลดการเขียนโค้ดซ้ำๆ ที่ทำงานคล้ายๆ กันที่ทุก Component ที่ต้องการโหลดข้อมูลจาก API
การประยุกต์ใช้ฟังก์ชัน hook ใน React สามารถทำอย่างอื่นได้อีกเยอะ เช่น การดึงค่าจาก Sessions, Cookies, การ tracking ค่าต่างๆ เช่น Mouse, Scroll Position เป็นต้น หากเราลองค้นใน npm จะพบว่า มีไลบรารี่อยู่ไม่น้อยที่ สร้างฟังก์ชัน hook มาไว้เพื่ออำนวยความสะดวกในการเขียนโปรแกรม เช่น react-use เราก็สามารถไปศึกษาจาก npm เหล่านั้นได้เลยว่าเขาประยุกต์ฟังก์ชัน hook กับงานอะไรบ้าง เผื่อจะเป็นประโยชน์กับเราได้บ้าง