React Hooks ที่มีประโยชน์ซึ่งคุณสามารถใช้ในโครงการของคุณ

เผยแพร่แล้ว: 2022-03-10
สรุปอย่างย่อ ↬ React ส่วนประกอบตามคลาสนั้นยุ่งเหยิง สับสน ยากสำหรับมนุษย์และเครื่องจักร แต่ก่อน React 16.8 คอมโพเนนต์ตามคลาสจำเป็นสำหรับโปรเจ็กต์ใดๆ ที่จำเป็นต้องมีสถานะ วิธีวงจรชีวิต และฟังก์ชันสำคัญอื่นๆ อีกมากมาย ทั้งหมดนี้เปลี่ยนไปด้วยการแนะนำ hooks ใน React 16.8 ตะขอเป็นตัวเปลี่ยนเกม พวกเขาทำให้ React ง่ายขึ้น ทำให้เรียบร้อยขึ้น เขียนและดีบั๊กได้ง่ายขึ้น และลดเส้นโค้งการเรียนรู้ด้วย

Hooks เป็นฟังก์ชันที่ให้คุณ เชื่อม ต่อหรือ ใช้งาน ฟีเจอร์ React ได้ พวกเขาได้รับการแนะนำที่ React Conf 2018 เพื่อจัดการกับปัญหาหลักสามประการของส่วนประกอบคลาส: wrapper hell, ส่วนประกอบขนาดใหญ่ และคลาสที่สับสน Hooks ให้พลังแก่ส่วนประกอบที่ใช้งานได้ของ React ทำให้สามารถพัฒนาแอพพลิเคชั่นทั้งหมดได้

ปัญหาดังกล่าวของส่วนประกอบในคลาสนั้นเชื่อมต่อกันและแก้ปัญหาอย่างใดอย่างหนึ่งโดยที่ปัญหาอื่นไม่สามารถทำให้เกิดปัญหาเพิ่มเติมได้ โชคดีที่ hooks แก้ปัญหาทั้งหมดได้อย่างง่ายดายและมีประสิทธิภาพ ในขณะที่สร้างพื้นที่สำหรับฟีเจอร์ที่น่าสนใจมากขึ้นใน React Hooks ไม่ได้แทนที่แนวคิดและคลาส React ที่มีอยู่แล้ว พวกเขาเพียงจัดเตรียม API เพื่อเข้าถึงโดยตรง

ทีม React ได้แนะนำตะขอหลายอันใน React 16.8 อย่างไรก็ตาม คุณสามารถใช้ hooks จากผู้ให้บริการบุคคลที่สามในแอปพลิเคชันของคุณ หรือแม้แต่สร้าง hook แบบกำหนดเองได้ ในบทช่วยสอนนี้ เราจะมาดูตะขอที่มีประโยชน์ใน React และวิธีใช้งาน เราจะพูดถึงตัวอย่างโค้ดต่างๆ ของแต่ละ hook และสำรวจวิธีสร้าง hook แบบกำหนดเอง

หมายเหตุ: บทช่วยสอนนี้ต้องการความเข้าใจพื้นฐานเกี่ยวกับ Javascript (ES6+) และ React

เพิ่มเติมหลังกระโดด! อ่านต่อด้านล่าง↓

แรงจูงใจเบื้องหลังตะขอ

ดังที่กล่าวไว้ก่อนหน้านี้ hooks ถูกสร้างขึ้นเพื่อแก้ปัญหาสามประการ: wrapper hell, ส่วนประกอบขนาดใหญ่ และคลาสที่สับสน ลองมาดูที่แต่ละเหล่านี้ในรายละเอียดเพิ่มเติม

แรปเปอร์เฮลล์

แอปพลิเคชันที่ซับซ้อนซึ่งสร้างด้วยส่วนประกอบของคลาสสามารถเรียกใช้งาน wrapper ได้อย่างง่ายดาย หากคุณตรวจสอบแอปพลิเคชันใน React Dev Tools คุณจะสังเกตเห็นส่วนประกอบที่ซ้อนกันอย่างลึกซึ้ง ทำให้การทำงานกับส่วนประกอบหรือดีบั๊กเป็นเรื่องยากมาก แม้ว่าปัญหาเหล่านี้จะแก้ไขได้ด้วย ส่วนประกอบที่มีลำดับสูงกว่า และ อุปกรณ์ประกอบฉาก แต่คุณต้องแก้ไขโค้ดเล็กน้อย ซึ่งอาจนำไปสู่ความสับสนในการใช้งานที่ซับซ้อน

Hook นั้นง่ายต่อการแบ่งปัน คุณไม่จำเป็นต้องแก้ไขส่วนประกอบของคุณก่อนที่จะใช้ตรรกะซ้ำ

ตัวอย่างที่ดีคือการใช้ Redux connect Higher Order Component (HOC) เพื่อสมัครสมาชิก Redux store เช่นเดียวกับ HOCs ทั้งหมด ในการใช้การเชื่อมต่อ HOC คุณต้องส่งออกส่วนประกอบควบคู่ไปกับฟังก์ชันลำดับที่สูงกว่าที่กำหนดไว้ ในกรณีของ connect เราจะมีแบบฟอร์มนี้

 export default connect(mapStateToProps, mapDispatchToProps)(MyComponent)

โดยที่ mapStateToProps และ mapDispatchToProps เป็นฟังก์ชันที่ต้องกำหนด

ในขณะที่ในยุคของ Hooks เราสามารถบรรลุผลลัพธ์เดียวกันได้อย่างง่ายดายและรัดกุมโดยใช้ Redux useSelector และ useDispatch hooks

ส่วนประกอบขนาดใหญ่

ส่วนประกอบของคลาสมักจะมีผลข้างเคียงและตรรกะของรัฐ เนื่องจากแอปพลิเคชันมีความซับซ้อนมากขึ้น เป็นเรื่องปกติที่ส่วนประกอบจะยุ่งเหยิงและสับสน ทั้งนี้เนื่องจากผลข้างเคียงคาดว่าจะจัดโดย วิธีวงจรชีวิต มากกว่าการทำงาน แม้ว่าจะสามารถแยกส่วนประกอบและทำให้ง่ายขึ้นได้ แต่สิ่งนี้มักจะทำให้เกิดนามธรรมในระดับที่สูงขึ้น

Hooks จัดระเบียบผลข้างเคียงตามการใช้งาน และสามารถแบ่งส่วนประกอบออกเป็นชิ้นๆ ตามการใช้งานได้

ชั้นเรียนที่สับสน

คลาสมักเป็นแนวคิดที่ยากกว่าฟังก์ชัน ส่วนประกอบตามคลาส React นั้นละเอียดและค่อนข้างยากสำหรับผู้เริ่มต้น หากคุณเพิ่งเริ่มใช้ Javascript คุณจะพบฟังก์ชันต่างๆ ที่ง่ายต่อการเริ่มต้นใช้งาน เนื่องจากไวยากรณ์ที่มีน้ำหนักเบาเมื่อเทียบกับคลาส ไวยากรณ์อาจทำให้สับสน บางครั้ง เป็นไปได้ที่จะลืมผูกตัวจัดการเหตุการณ์ที่อาจทำลายรหัส

React แก้ปัญหานี้ด้วยส่วนประกอบและ hooks ที่ใช้งานได้ ทำให้นักพัฒนาสามารถโฟกัสที่โปรเจ็กต์ได้ มากกว่าที่จะเน้นที่รูปแบบโค้ด

ตัวอย่างเช่น ส่วนประกอบ React สองส่วนต่อไปนี้จะให้ผลลัพธ์ที่เหมือนกันทุกประการ

 import React, { Component } from "react"; export default class App extends Component { constructor(props) { super(props); this.state = { num: 0 }; this.incrementNumber = this.incrementNumber.bind(this); } incrementNumber() { this.setState({ num: this.state.num + 1 }); } render() { return ( <div> <h1>{this.state.num}</h1> <button onClick={this.incrementNumber}>Increment</button> </div> ); } }
 import React, { useState } from "react"; export default function App() { const [num, setNum] = useState(0); function incrementNumber() { setNum(num + 1); } return ( <div> <h1>{num}</h1> <button onClick={incrementNumber}>Increment</button> </div> ); }

ตัวอย่างแรกเป็นส่วนประกอบตามคลาส ในขณะที่ตัวอย่างที่สองเป็นส่วนประกอบที่ใช้งานได้ แม้ว่านี่จะเป็นเพียงตัวอย่างง่ายๆ แต่สังเกตว่าตัวอย่างแรกนั้นเปรียบเทียบกับตัวอย่างที่สองที่ปลอมแปลงได้อย่างไร

อนุสัญญาและกฎของ Hooks

ก่อนที่จะเจาะลึกลงไปใน hooks ต่างๆ การพิจารณาอนุสัญญาและกฎเกณฑ์ที่ใช้กับพวกเขาอาจเป็นประโยชน์ นี่คือกฎเกณฑ์บางประการที่ใช้กับตะขอ

  1. หลักการตั้งชื่อของ hooks ควรเริ่มต้นด้วยการ use คำนำหน้า ดังนั้น เราสามารถมี useState , useEffect เป็นต้น หากคุณใช้โปรแกรมแก้ไขโค้ดสมัยใหม่ เช่น Atom และ VSCode ปลั๊กอิน ESLint อาจเป็นคุณสมบัติที่มีประโยชน์มากสำหรับ React hooks ปลั๊กอินมีคำเตือนและคำแนะนำที่เป็นประโยชน์เกี่ยวกับแนวทางปฏิบัติที่ดีที่สุด
  2. ต้องเรียกใช้ hook ที่ระดับบนสุดของส่วนประกอบ ก่อนคำสั่ง return ไม่สามารถเรียกได้ภายในคำสั่งแบบมีเงื่อนไข ลูป หรือฟังก์ชันที่ซ้อนกัน
  3. ต้องเรียก hook จากฟังก์ชัน React (ภายในส่วนประกอบ React หรือ hook อื่น) ไม่ควรเรียกจากฟังก์ชัน Vanilla JS

การใช้งาน useState Hook

useState hook เป็น React hook พื้นฐานและมีประโยชน์มากที่สุด เช่นเดียวกับตะขอในตัวอื่น ๆ ตะขอนี้จะต้องนำเข้าจาก react เพื่อใช้ในแอปพลิเคชันของเรา

 import {useState} from 'react'

ในการเริ่มต้นสถานะ เราต้องประกาศทั้งสถานะและฟังก์ชันตัวอัปเดตและส่งผ่านค่าเริ่มต้น

 const [state, updaterFn] = useState('')

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

ตัวอย่างเช่น ตั้งค่าสถานะเพื่อเก็บค่าการนับ

 const [count, setCount] = useState(0)

ขอให้สังเกตว่าค่าเริ่มต้นของสถานะ count ของเราถูกตั้งค่าเป็น 0 และไม่ใช่สตริงว่าง กล่าวอีกนัยหนึ่ง เราสามารถเริ่มต้นสถานะของเราเป็นตัวแปร JavaScript ชนิดใดก็ได้ ได้แก่ ตัวเลข สตริง บูลีน อาร์เรย์ วัตถุ และแม้แต่ BigInt มีความแตกต่างที่ชัดเจนระหว่างสถานะการตั้งค่าด้วย useState hook และสถานะส่วนประกอบตามคลาส เป็นที่น่าสังเกตว่า useState hook ส่งคืนอาร์เรย์หรือที่เรียกว่าตัวแปรสถานะ และในตัวอย่างข้างต้น เราได้ทำลายโครงสร้างอาร์เรย์เป็น state และฟังก์ชันตัว updater เดต

การแสดงผลส่วนประกอบ

การตั้งค่าสถานะด้วย useState hook ทำให้ส่วนประกอบที่เกี่ยวข้องแสดงผลใหม่ อย่างไรก็ตาม สิ่งนี้จะเกิดขึ้นก็ต่อเมื่อ React ตรวจพบความแตกต่างระหว่างสถานะก่อนหน้าหรือสถานะเก่ากับสถานะใหม่ React ทำการเปรียบเทียบสถานะโดยใช้อัลกอริทึม Javascript Object.is

การตั้งค่าสถานะด้วย useState

สถานะ count ของเราสามารถตั้งค่าเป็นค่าสถานะใหม่โดยเพียงแค่ส่งค่าใหม่ไปยังฟังก์ชัน setCount updater ดังนี้ setCount(newValue)

วิธีนี้ใช้ได้เมื่อเราไม่ต้องการอ้างอิงค่าสถานะก่อนหน้า หากเราต้องการทำเช่นนั้น เราจำเป็นต้องส่งผ่านฟังก์ชันไปยังฟังก์ชัน setCount

สมมติว่าเราต้องการเพิ่ม 5 ให้กับตัวแปรการ count ของเราทุกครั้งที่มีการคลิกปุ่ม เราสามารถทำสิ่งต่อไปนี้ได้

 import {useState} from 'react' const CountExample = () => { // initialize our count state const [count, setCount] = useState(0) // add 5 to to the count previous state const handleClick = () =>{ setCount(prevCount => prevCount + 5) } return( <div> <h1>{count} </h1> <button onClick={handleClick}>Add Five</button> </div> ) } export default CountExample

ในโค้ดด้านบนนี้ ก่อนอื่นเรานำเข้า useState hook จาก react จากนั้นจึงเริ่มต้นสถานะการ count ด้วยค่าเริ่มต้นเป็น 0 เราได้สร้างตัวจัดการ onClick เพื่อเพิ่มมูลค่าของการ count ขึ้น 5 ทุกครั้งที่มีการคลิกปุ่ม จากนั้นเราแสดงผลในแท็ก h1

การตั้งค่าอาร์เรย์และสถานะของอ็อบเจ็กต์

สถานะของอาร์เรย์และอ็อบเจ็กต์สามารถตั้งค่าได้ในลักษณะเดียวกับประเภทข้อมูลอื่นๆ อย่างไรก็ตาม หากเราต้องการรักษาค่าที่มีอยู่ เราจำเป็นต้องใช้ตัวดำเนินการสเปรด ES6 เมื่อตั้งค่าสถานะ

ตัวดำเนินการกระจายใน Javascript ใช้เพื่อสร้างวัตถุใหม่จากวัตถุที่มีอยู่แล้ว สิ่งนี้มีประโยชน์เนื่องจาก React เปรียบเทียบสถานะกับการดำเนินการ Object.is แล้วแสดงผลใหม่ตามนั้น

ลองพิจารณาโค้ดด้านล่างสำหรับการตั้งค่าสถานะเมื่อคลิกปุ่ม

 import {useState} from 'react' const StateExample = () => { //initialize our array and object states const [arr, setArr] = useState([2, 4]) const [obj, setObj] = useState({num: 1, name: 'Desmond'}) // set arr to the new array values const handleArrClick = () =>{ const newArr = [1, 5, 7] setArr([...arr, ...newArr]) } // set obj to the new object values const handleObjClick = () =>{ const newObj = {name: 'Ifeanyi', age: 25} setObj({...obj, ...newObj}) } return( <div> <button onClick ={handleArrClick}>Set Array State</button> <button onClick ={handleObjClick}>Set Object State</button> </div> ) } export default StateExample

ในโค้ดด้านบนนี้ เราได้สร้างสองสถานะ arr และ obj และกำหนดค่าเริ่มต้นให้กับอาร์เรย์และค่าอ็อบเจ็กต์ตามลำดับ จากนั้นเราสร้างตัวจัดการ onClick ที่เรียกว่า handleArrClick และ handleObjClick เพื่อตั้งค่าสถานะของอาร์เรย์และวัตถุตามลำดับ เมื่อ handleArrClick ทำงาน เราจะเรียก setArr และใช้ตัวดำเนินการการแพร่กระจาย ES6 เพื่อกระจายค่าอาร์เรย์ที่มีอยู่แล้วและเพิ่ม newArr เข้าไป

เราทำสิ่งเดียวกันกับตัวจัดการ handleObjClick ที่นี่เราเรียกว่า setObj กระจายค่าอ็อบเจ็กต์ที่มีอยู่โดยใช้โอเปอเรเตอร์การแพร่กระจาย ES6 และอัปเดตค่าของ name และ age

Async ลักษณะการ useState

ดังที่เราได้เห็นแล้ว เราตั้งค่าสถานะด้วย useState โดยส่งค่าใหม่ไปยังฟังก์ชันตัวอัปเดต หากมีการเรียกตัวอัปเดตหลายครั้ง ค่าใหม่จะถูกเพิ่มลงในคิวและแสดงผลใหม่ตามนั้นโดยใช้การเปรียบเทียบ JavaScript Object.is

สถานะได้รับการปรับปรุงแบบอะซิงโครนัส ซึ่งหมายความว่า สถานะใหม่จะถูกเพิ่มในสถานะรอดำเนินการก่อน จากนั้น สถานะจะได้รับการอัปเดต ดังนั้น คุณอาจยังคงได้รับค่าสถานะเดิมหากคุณเข้าถึงสถานะทันทีที่ตั้งค่าไว้

ลองพิจารณาตัวอย่างต่อไปนี้เพื่อสังเกตพฤติกรรมนี้

ในโค้ดด้านบน เราได้สร้างสถานะการ count โดยใช้ useState hook จากนั้น เราได้สร้างตัวจัดการ onClick เพื่อเพิ่มสถานะการ count ทุกครั้งที่มีการคลิกปุ่ม สังเกตว่าแม้ว่าสถานะการ count เพิ่มขึ้น ดังที่แสดงในแท็ก h2 สถานะก่อนหน้าจะยังคงบันทึกอยู่ในคอนโซล นี่เป็นเพราะลักษณะ async ของเบ็ด

หากเราต้องการรับสถานะใหม่ เราสามารถจัดการได้ในลักษณะเดียวกับที่เราจะจัดการกับฟังก์ชัน async นี่เป็นวิธีหนึ่งในการทำเช่นนั้น

ที่นี่ เราจัดเก็บสร้าง newCountValue เพื่อเก็บค่าการนับที่อัปเดตแล้วตั้งค่าสถานะการ count ด้วยค่าที่อัปเดต จากนั้น เราบันทึกค่าการนับที่อัปเดตในคอนโซล

useEffect การใช้งาน

useEffect เป็นอีกหนึ่ง React hook ที่สำคัญที่ใช้ในโปรเจ็กต์ส่วนใหญ่ มันทำสิ่งที่คล้ายกันกับเมธอดของ componentDidMount , componentWillUnmount และ componentDidUpdate ของคอมโพเนนต์ตามคลาส useEffect เปิดโอกาสให้เราเขียนโค้ดที่จำเป็นซึ่งอาจมีผลข้างเคียงกับแอปพลิเคชัน ตัวอย่างของเอฟเฟกต์ดังกล่าว ได้แก่ การบันทึก การสมัครสมาชิก การกลายพันธุ์ ฯลฯ

ผู้ใช้สามารถตัดสินใจได้ว่าจะใช้ useEffect เมื่อใด อย่างไรก็ตาม หากไม่ได้ตั้งค่าไว้ ผลข้างเคียงจะทำงานในทุกการเรนเดอร์หรือการแสดงซ้ำ

พิจารณาตัวอย่างด้านล่าง

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ console.log(count) }) return( <div> ... </div> ) }

ในโค้ดด้านบนนี้ เราเพียงแค่บันทึก count ใน useEffect สิ่งนี้จะทำงานหลังจากการเรนเดอร์องค์ประกอบทุกครั้ง

บางครั้ง เราอาจต้องการเรียกใช้ hook หนึ่งครั้ง (บน mount) ในส่วนประกอบของเรา เราสามารถทำได้โดยระบุพารามิเตอร์ตัวที่สองเพื่อใช้ useEffect

 import {useState, useEffect} from 'react' const App = () =>{ const [count, setCount] = useState(0) useEffect(() =>{ setCount(count + 1) }, []) return( <div> <h1>{count}</h1> ... </div> ) }

ตะขอ useEffect มีสองพารามิเตอร์ พารามิเตอร์แรกคือฟังก์ชันที่เราต้องการเรียกใช้ในขณะที่พารามิเตอร์ที่สองคืออาร์เรย์ของการพึ่งพา หากไม่ได้ระบุพารามิเตอร์ที่สองไว้ เบ็ดจะทำงานอย่างต่อเนื่อง

โดยการส่งวงเล็บเหลี่ยมว่างไปยังพารามิเตอร์ที่สองของ hook เราสั่งให้ React เรียกใช้ useEffect hook เพียงครั้งเดียวบน mount ค่านี้จะแสดงค่า 1 ในแท็ก h1 เนื่องจากการนับจะอัปเดตหนึ่งครั้งจาก 0 เป็น 1 เมื่อคอมโพเนนต์ติดตั้ง

นอกจากนี้เรายังสามารถทำให้ผลข้างเคียงของเราทำงานทุกครั้งที่มีการเปลี่ยนแปลงค่าที่ขึ้นต่อกัน ซึ่งสามารถทำได้โดยส่งค่าเหล่านี้ในรายการการพึ่งพา

ตัวอย่างเช่น เราสามารถทำให้ useEffect ทำงานทุกครั้งที่ count การเปลี่ยนแปลงดังนี้

 import { useState, useEffect } from "react"; const App = () => { const [count, setCount] = useState(0); useEffect(() => { console.log(count); }, [count]); return ( <div> <button onClick={() => setCount(count + 1)}>Increment</button> </div> ); }; export default App;

useEffect ด้านบนจะทำงานเมื่อตรงตามเงื่อนไขสองข้อนี้

  1. บนเมานต์ — หลังจากสร้างองค์ประกอบแล้ว
  2. เมื่อมูลค่าการ count เปลี่ยนไป

บนเมานต์ นิพจน์ console.log จะทำงานและบันทึก count เป็น 0 เมื่อการ count ได้รับการอัปเดต จะเป็นไปตามเงื่อนไขที่สอง ดังนั้น useEffect จะทำงานอีกครั้ง สิ่งนี้จะดำเนินต่อไปทุกครั้งที่มีการคลิกปุ่ม

เมื่อเราจัดเตรียมอาร์กิวเมนต์ที่สองให้กับ useEffect แล้ว เราคาดหวังให้เราส่งต่อการพึ่งพาทั้งหมดไปที่มัน หากคุณติดตั้ง ESLINT ไว้ ระบบจะแสดงข้อผิดพลาดของผ้าสำลีหากไม่มีการส่งต่อการพึ่งพาใด ๆ ไปยังรายการพารามิเตอร์ นอกจากนี้ยังอาจทำให้ผลข้างเคียงทำงานโดยไม่คาดคิด โดยเฉพาะอย่างยิ่งหากขึ้นอยู่กับพารามิเตอร์ที่ไม่ผ่าน

ทำความสะอาดเอฟเฟกต์

useEffect ยังช่วยให้เราสามารถล้างข้อมูลทรัพยากรก่อนที่ส่วนประกอบจะยกเลิกการต่อเชื่อม ซึ่งอาจจำเป็นเพื่อป้องกันการรั่วไหลของหน่วยความจำและทำให้แอปพลิเคชันมีประสิทธิภาพมากขึ้น ในการดำเนินการนี้ เราจะคืนค่าฟังก์ชันล้างข้อมูลเมื่อสิ้นสุดเบ็ด

 useEffect(() => { console.log('mounted') return () => console.log('unmounting... clean up here') })

ตะขอ useEffect ด้านบนจะบันทึกการ mounted เมื่อติดตั้งส่วนประกอบ กำลังยกเลิกการต่อเชื่อม… การล้างข้อมูลที่นี่ จะถูกบันทึกเมื่อส่วนประกอบยกเลิกการต่อเชื่อม สิ่งนี้สามารถเกิดขึ้นได้เมื่อส่วนประกอบถูกลบออกจาก UI

กระบวนการล้างข้อมูลมักจะเป็นไปตามแบบฟอร์มด้านล่าง

 useEffect(() => { //The effect we intend to make effect //We then return the clean up return () => the cleanup/unsubscription })

แม้ว่าคุณอาจไม่พบกรณีการใช้งานจำนวนมากสำหรับการสมัครรับข้อมูล useEffect แต่ก็มีประโยชน์เมื่อต้องรับมือกับการสมัครรับข้อมูลและตัวจับเวลา โดยเฉพาะอย่างยิ่ง เมื่อต้องรับมือกับซ็อกเก็ตเว็บ คุณอาจต้องยกเลิกการต่อเชื่อมจากเครือข่ายเพื่อประหยัดทรัพยากรและปรับปรุงประสิทธิภาพเมื่อส่วนประกอบยกเลิกการต่อเชื่อม

การดึงและดึงข้อมูลด้วย useEffect

กรณีการใช้งานทั่วไปอย่างหนึ่งของ useEffect hook คือการดึงข้อมูลและดึงข้อมูลจาก API ล่วงหน้า

เพื่อแสดงสิ่งนี้ เราจะใช้ข้อมูลผู้ใช้ปลอมที่ฉันสร้างจาก JSONPlaceholder เพื่อดึงข้อมูลด้วย useEffect hook

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [users, setUsers] = useState([]); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/users"; useEffect(() => { const fetchUsers = async () => { const { data } = await axios.get(endPoint); setUsers(data); }; fetchUsers(); }, []); return ( <div className="App"> {users.map((user) => ( <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> ))} </div> ); }

ในโค้ดด้านบนนี้ เราได้สร้างสถานะ users โดยใช้ useState hook จากนั้นเราดึงข้อมูลจาก API โดยใช้ Axios นี่เป็นกระบวนการแบบอะซิงโครนัส ดังนั้นเราจึงใช้ฟังก์ชัน async/await เราสามารถใช้จุดและไวยากรณ์ได้เช่นกัน เนื่องจากเราดึงรายชื่อผู้ใช้ เราเพียงแค่จับคู่ผ่านเพื่อแสดงข้อมูล

ขอให้สังเกตว่าเราส่งพารามิเตอร์ว่างไปยังเบ็ด เพื่อให้แน่ใจว่ามีการเรียกเพียงครั้งเดียวเมื่อส่วนประกอบติดตั้ง

นอกจากนี้เรายังสามารถ ดึง ข้อมูลเมื่อเงื่อนไขบางอย่างเปลี่ยนแปลง เราจะแสดงสิ่งนี้ในรหัสด้านล่าง

 import { useEffect, useState } from "react"; import axios from "axios"; export default function App() { const [userIDs, setUserIDs] = useState([]); const [user, setUser] = useState({}); const [currentID, setCurrentID] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/userdata/users"; useEffect(() => { axios.get(endPoint).then(({ data }) => setUserIDs(data)); }, []); useEffect(() => { const fetchUserIDs = async () => { const { data } = await axios.get(`${endPoint}/${currentID}`}); setUser(data); }; fetchUserIDs(); }, [currentID]); const moveToNextUser = () => { setCurrentID((prevId) => (prevId < userIDs.length ? prevId + 1 : prevId)); }; const moveToPrevUser = () => { setCurrentID((prevId) => (prevId === 1 ? prevId : prevId - 1)); }; return ( <div className="App"> <div> <h2>{user.name}</h2> <p>Occupation: {user.job}</p> <p>Sex: {user.sex}</p> </div> <button onClick={moveToPrevUser}>Prev</button> <button onClick={moveToNextUser}>Next</button> </div> ); }

ที่นี่เราสร้าง useEffect hooks สองอัน ในอันแรก เราใช้จุดแล้วไวยากรณ์เพื่อรับผู้ใช้ทั้งหมดจาก API ของเรา นี่เป็นสิ่งจำเป็นในการกำหนดจำนวนผู้ใช้

จากนั้นเราได้สร้าง useEffect hook ใหม่เพื่อรับผู้ใช้ตาม id useEffect นี้จะดึงข้อมูลทุกครั้งที่ ID เปลี่ยนแปลง เพื่อให้แน่ใจว่าสิ่งนี้ เราได้ส่ง id ในรายการการพึ่งพา

ต่อไป เราได้สร้างฟังก์ชันเพื่ออัปเดตค่าของ id ของเราทุกครั้งที่มีการคลิกปุ่ม เมื่อค่าของ id เปลี่ยนไป useEffect จะทำงานอีกครั้งและดึงข้อมูลกลับมา

หากเราต้องการ เราสามารถล้างหรือยกเลิกโทเค็นตามสัญญาใน Axios ได้ เราสามารถทำได้ด้วยวิธีการล้างข้อมูลที่กล่าวถึงข้างต้น

 useEffect(() => { const source = axios.CancelToken.source(); const fetchUsers = async () => { const { data } = await axios.get(`${endPoint}/${num}`, { cancelToken: source.token }); setUser(data); }; fetchUsers(); return () => source.cancel(); }, [num]);

ที่นี่ เราส่งโทเค็นของ Axios เป็นพารามิเตอร์ที่สองไปยัง axios.get เมื่อส่วนประกอบ unmount เราจะยกเลิกการสมัครสมาชิกโดยเรียกวิธีการยกเลิกของวัตถุต้นทาง

useReducer การใช้งาน

เบ็ด useReducer เป็นเบ็ด React ที่มีประโยชน์มากซึ่งทำหน้าที่คล้ายกับเบ็ด useState ตามเอกสารประกอบของ React ควรใช้ hook นี้เพื่อจัดการกับตรรกะที่ซับซ้อนกว่า hook state ของ useState เป็นที่น่าสังเกตว่า useState hook นั้นถูกใช้งานภายในด้วย useReducer hook

hook ใช้ตัวลดขนาดเป็นอาร์กิวเมนต์ และสามารถเลือกใช้สถานะเริ่มต้นและฟังก์ชัน init เป็นอาร์กิวเมนต์ได้

 const [state, dispatch] = useReducer(reducer, initialState, init)

ที่นี่ init เป็นฟังก์ชันและมันถูกใช้เมื่อใดก็ตามที่เราต้องการสร้างสถานะเริ่มต้นอย่างเกียจคร้าน

มาดูวิธีการใช้ useReducer hook โดยการสร้างแอพ to-do ง่ายๆ ดังแสดงในแซนด์บ็อกซ์ด้านล่าง

ตัวอย่างสิ่งที่ต้องทำ

ก่อนอื่น เราควรสร้างตัวลดของเราเพื่อยึดสถานะ

 export const ADD_TODO = "ADD_TODO"; export const REMOVE_TODO = "REMOVE_TODO"; export const COMPLETE_TODO = "COMPLETE_TODO"; const reducer = (state, action) => { switch (action.type) { case ADD_TODO: const newTodo = { id: action.id, text: action.text, completed: false }; return [...state, newTodo]; case REMOVE_TODO: return state.filter((todo) => todo.id !== action.id); case COMPLETE_TODO: const completeTodo = state.map((todo) => { if (todo.id === action.id) { return { ...todo, completed: !todo.completed }; } else { return todo; } }); return completeTodo; default: return state; } }; export default reducer;

เราสร้างค่าคงที่สามค่าที่สอดคล้องกับประเภทการกระทำของเรา เราสามารถใช้สตริงได้โดยตรง แต่วิธีนี้ดีกว่าเพื่อหลีกเลี่ยงการพิมพ์ผิด

จากนั้นเราก็สร้างฟังก์ชันรีดิวเซอร์ของเรา เช่นเดียวกับใน Redux ตัวลดต้องใช้สถานะและวัตถุการกระทำ แต่ต่างจาก Redux เราไม่จำเป็นต้องเริ่มต้นตัวลดของเราที่นี่

นอกจากนี้ สำหรับกรณีการใช้งานด้านการจัดการของรัฐจำนวนมาก useReducer พร้อมกับการ dispatch ที่เปิดเผยผ่านบริบทสามารถเปิดใช้งานแอปพลิเคชันขนาดใหญ่ขึ้นเพื่อเริ่มการทำงาน อัปเดต state และรับฟังได้

จากนั้นเราใช้คำสั่ง switch เพื่อตรวจสอบประเภทการกระทำที่ส่งผ่านโดยผู้ใช้ หากประเภทการดำเนินการคือ ADD_TODO เราต้องการส่งต่อสิ่งที่ต้องทำใหม่และหากเป็น REMOVE_TODO เราต้องการกรองสิ่งที่ต้องทำและลบรายการที่ตรงกับ id ที่ผู้ใช้ส่งผ่าน หากเป็น COMPLETE_TODO เราต้องการแมปผ่านสิ่งที่ต้องทำและสลับรายการที่มี id ที่ส่งมาจากผู้ใช้

นี่คือไฟล์ App.js ที่เราใช้งานตัว reducer ขนาด

 import { useReducer, useState } from "react"; import "./styles.css"; import reducer, { ADD_TODO, REMOVE_TODO, COMPLETE_TODO } from "./reducer"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = [ { id: id, text: "First Item", completed: false } ]; //We could also pass an empty array as the initial state //const initialState = [] const [state, dispatch] = useReducer(reducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); dispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="App"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit">+</button> </form> <div className="todos"> {state.map((todo) => ( <div key={todo.id} className="todoItem"> <p className={todo.completed && "strikethrough"}>{todo.text}</p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ))} </div> </div> ); }

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

อย่างไรก็ตาม ความมหัศจรรย์เกิดขึ้นเพราะเรากำลังใช้เบ็ด useReducer เบ็ดนี้ยอมรับตัวลดและสถานะเริ่มต้นและส่งคืนสถานะและฟังก์ชันการจัดส่ง ที่นี่ ฟังก์ชันการจัดส่งมีจุดประสงค์เดียวกับฟังก์ชัน setter สำหรับ useState hook และเราสามารถเรียกมันว่าอะไรก็ได้ที่เราต้องการแทนที่จะใช้ dispatch

ในการแสดงรายการสิ่งที่ต้องทำ เราเพียงแค่จับคู่ผ่านรายการสิ่งที่ต้องทำที่ส่งคืนในอ็อบเจกต์สถานะของเราดังที่แสดงในโค้ดด้านบน

นี่แสดงให้เห็นถึงพลังของ useReducer hook เราสามารถบรรลุฟังก์ชันนี้ได้ด้วย useState hook แต่ดังที่คุณเห็นจากตัวอย่างด้านบน useReducer hook ช่วยให้เราจัดการสิ่งต่างๆ ให้เรียบร้อยยิ่งขึ้น useReducer มักจะเป็นประโยชน์เมื่อ state object เป็นโครงสร้างที่ซับซ้อนและได้รับการปรับปรุงในรูปแบบต่างๆ เมื่อเทียบกับการแทนที่ค่าอย่างง่าย นอกจากนี้ เมื่อฟังก์ชันการอัปเดตเหล่านี้ซับซ้อนมากขึ้น useReducer จะทำให้ง่ายต่อการเก็บความซับซ้อนทั้งหมดไว้ในฟังก์ชันรีดิวเซอร์ (ซึ่งเป็นฟังก์ชัน JS ล้วนๆ) ทำให้ง่ายต่อการเขียนการทดสอบสำหรับฟังก์ชันรีดิวเซอร์เพียงอย่างเดียว

เราอาจส่งข้อโต้แย้งที่สามไปยัง useReducer hook เพื่อสร้างสถานะเริ่มต้นอย่างเกียจคร้าน ซึ่งหมายความว่าเราสามารถคำนวณสถานะเริ่มต้นในฟังก์ชัน init

ตัวอย่างเช่น เราสามารถสร้างฟังก์ชัน init ได้ดังนี้:

 const initFunc = () => [ { id: id, text: "First Item", completed: false } ]

แล้วส่งต่อไปยังตะขอ useReducer ของเรา

 const [state, dispatch] = useReducer(reducer, initialState, initFunc)

ถ้าเราทำเช่นนี้ initFunc จะแทนที่ initialState ที่เราให้ไว้ และสถานะเริ่มต้นจะถูกคำนวณอย่างเกียจคร้าน

useContext Hook

React Context API ให้วิธีการแบ่งปันสถานะหรือข้อมูลทั่วทั้งโครงสร้างส่วนประกอบ React API มีให้บริการใน React เป็นคุณลักษณะทดลองมาระยะหนึ่งแล้ว แต่ก็ปลอดภัยที่จะใช้ใน React 16.3.0 API ทำให้การแบ่งปันข้อมูลระหว่างส่วนประกอบต่างๆ ทำได้ง่ายในขณะที่ขจัดการเจาะเสา

แม้ว่าคุณจะสามารถนำบริบทการโต้ตอบไปใช้กับแอปพลิเคชันทั้งหมดได้ แต่ก็เป็นไปได้ที่จะนำไปใช้กับส่วนหนึ่งของแอปพลิเคชัน

ในการใช้ hook คุณต้องสร้างบริบทโดยใช้ React.createContext ก่อน แล้วจึงสามารถส่งบริบทนี้ไปที่ hook ได้

เพื่อสาธิตการใช้ useContext hook ให้สร้างแอปง่ายๆ ที่จะเพิ่มขนาดฟอนต์ตลอดทั้งแอปพลิเคชันของเรา

มาสร้างบริบทของเราในไฟล์ context.js

 import { createContext } from "react"; //Here, we set the initial fontSize as 16. const fontSizeContext = createContext(16); export default fontSizeContext;

ที่นี่ เราสร้างบริบทและส่งค่าเริ่มต้น 16 ไปยังบริบทนั้น จากนั้นจึงส่งออกบริบท ต่อไป มาเชื่อมโยงบริบทของเรากับแอปพลิเคชันของเรากัน

 import FontSizeContext from "./context"; import { useState } from "react"; import PageOne from "./PageOne"; import PageTwo from "./PageTwo"; const App = () => { const [size, setSize] = useState(16); return ( <FontSizeContext.Provider value={size}> <PageOne /> <PageTwo /> <button onClick={() => setSize(size + 5)}>Increase font</button> <button onClick={() => setSize((prevSize) => Math.min(11, prevSize - 5)) } > Decrease font </button> </FontSizeContext.Provider> ); }; export default App;

ในโค้ดด้านบน เราได้รวมโครงสร้างส่วนประกอบทั้งหมดของเราด้วย FontSizeContext.Provider และส่งผ่าน size ไปยังค่าที่เหมาะสม ที่นี่ size เป็นสถานะที่สร้างด้วย useState hook ซึ่งช่วยให้เราสามารถเปลี่ยนค่า prop เมื่อใดก็ตามที่สถานะ size เปลี่ยนแปลง ด้วยการห่อส่วนประกอบทั้งหมดด้วย Provider เราสามารถเข้าถึงบริบทได้ทุกที่ในแอปพลิเคชันของเรา

ตัวอย่างเช่น เราเข้าถึงบริบทใน <PageOne /> และ <PageTwo /> ด้วยเหตุนี้ ขนาดแบบอักษรจะเพิ่มขึ้นในสององค์ประกอบนี้ เมื่อเราเพิ่มจากไฟล์ App.js เราสามารถเพิ่มหรือลดขนาดฟอนต์จากปุ่มต่างๆ ดังที่แสดงด้านบน และเมื่อเราทำเสร็จแล้ว ขนาดฟอนต์จะเปลี่ยนไปตลอดทั้งแอพพลิเคชั่น

 import { useContext } from "react"; import context from "./context"; const PageOne = () => { const size = useContext(context); return <p style={{ fontSize: `${size}px` }}>Content from the first page</p>; }; export default PageOne;

ที่นี่ เราเข้าถึงบริบทโดยใช้เบ็ด useContext จากคอมโพเนนต์ PageOne ของเรา จากนั้นเราใช้บริบทนี้เพื่อตั้งค่าคุณสมบัติขนาดฟอนต์ของเรา ขั้นตอนที่คล้ายกันนี้ใช้กับไฟล์ PageTwo.js

ธีมหรือการกำหนดค่าระดับแอปที่มีลำดับสูงกว่าอื่นๆ เป็นตัวเลือกที่ดีสำหรับบริบท

การใช้ useContext และ useReducer

เมื่อใช้กับ useReducer hook useContext ช่วยให้เราสร้างระบบการจัดการสถานะของเราเอง เราสามารถสร้างสถานะทั่วโลกและจัดการได้อย่างง่ายดายในแอปพลิเคชันของเรา

มาปรับปรุงแอปพลิเคชันที่ต้องทำของเราโดยใช้บริบท API

ตามปกติ เราต้องสร้าง todoContext ในไฟล์ todoContext.js

 import { createContext } from "react"; const initialState = []; export default createContext(initialState);

ที่นี่เราสร้างบริบทโดยส่งค่าเริ่มต้นของอาร์เรย์ที่ว่างเปล่า จากนั้นเราส่งออกบริบท

มาสร้างโครงสร้างไฟล์ App.js ใหม่โดยแยกรายการสิ่งที่ต้องทำและรายการ

 import { useReducer, useState } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import TodoList from "./TodoList"; export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <div className="app"> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </TodoContext.Provider> ); }

ที่นี่ เราห่อไฟล์ App.js ด้วย TodoContext.Provider จากนั้นเราก็ส่งค่าที่ส่งคืนของ todoReducer ของเราไป ทำให้สามารถเข้าถึงสถานะและฟังก์ชันการ dispatch ของตัวลดขนาดได้ทั่วทั้งแอปพลิเคชันของเรา

จากนั้นเราแยกการแสดงสิ่งที่ต้องทำออกเป็นส่วนประกอบ TodoList เราทำสิ่งนี้โดยไม่ต้องเจาะเสาด้วย Context API มาดูไฟล์ TodoList.js กัน

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import Todo from "./Todo"; const TodoList = () => { const [state] = useContext(TodoContext); return ( <div className="todos"> {state.map((todo) => ( <Todo key={todo.id} todo={todo} /> ))} </div> ); }; export default TodoList;

การใช้การทำลายอาร์เรย์ทำให้เราสามารถเข้าถึงสถานะ (ออกจากฟังก์ชันการจัดส่ง) จากบริบทโดยใช้เบ็ด useContext จากนั้นเราสามารถแมปผ่านรัฐและแสดงรายการสิ่งที่ต้องทำ เรายังคงดึงข้อมูลนี้ในองค์ประกอบ Todo ฟังก์ชันแผนที่ ES6+ กำหนดให้เราต้องส่งคีย์ที่ไม่ซ้ำ และเนื่องจากเราต้องการสิ่งที่ต้องทำเฉพาะ เราจึงส่งต่อคีย์นั้นควบคู่ไปด้วย

มาดูส่วนประกอบ Todo กัน

 import React, { useContext } from "react"; import TodoContext from "./todoContext"; import { REMOVE_TODO, COMPLETE_TODO } from "./todoReducer"; const Todo = ({ todo }) => { const [, dispatch] = useContext(TodoContext); const removeTodo = (id) => { dispatch({ type: REMOVE_TODO, id }); }; const completeTodo = (id) => { dispatch({ type: COMPLETE_TODO, id }); }; return ( <div className="todoItem"> <p className={todo.completed ? "strikethrough" : "nostrikes"}> {todo.text} </p> <span onClick={() => removeTodo(todo.id)}>✕</span> <span onClick={() => completeTodo(todo.id)}>✓</span> </div> ); }; export default Todo;

อีกครั้งโดยใช้การทำลายอาร์เรย์ เราเข้าถึงฟังก์ชันการสั่งงานจากบริบท ซึ่งช่วยให้เราสามารถกำหนดฟังก์ชัน completeTodo และ removeTodo ตามที่ได้อธิบายไว้ในส่วน useReducer ด้วย todo prop ที่ส่งผ่านจาก todoList.js เราสามารถแสดงรายการสิ่งที่ต้องทำ เรายังทำเครื่องหมายว่าเสร็จแล้วและลบสิ่งที่ต้องทำออกได้ตามที่เห็นสมควร

นอกจากนี้ยังสามารถซ้อนผู้ให้บริการบริบทมากกว่าหนึ่งรายในรูทของแอปพลิเคชันของเรา ซึ่งหมายความว่าเราสามารถใช้มากกว่าหนึ่งบริบทเพื่อทำหน้าที่ต่างๆ ในแอปพลิเคชันได้

เพื่อแสดงสิ่งนี้ ให้เพิ่มธีมในตัวอย่างสิ่งที่ต้องทำ

นี่คือสิ่งที่เรากำลังจะสร้าง

อีกครั้ง เราต้องสร้าง themeContext ในการดำเนินการนี้ ให้สร้างไฟล์ themeContext.js และเพิ่มโค้ดต่อไปนี้

 import { createContext } from "react"; import colors from "./colors"; export default createContext(colors.light);

ที่นี่ เราสร้างบริบทและส่งผ่าน colors.light เป็นค่าเริ่มต้น มากำหนดสีด้วยคุณสมบัตินี้ในไฟล์ colors.js

 const colors = { light: { backgroundColor: "#fff", color: "#000" }, dark: { backgroundColor: "#000", color: "#fff" } }; export default colors;

ในโค้ดด้านบนนี้ เราได้สร้างอ็อบเจกต์ colors ที่มีคุณสมบัติแสงและความมืด แต่ละคุณสมบัติมีอ็อบเจกต์ backgroundColor และ color

ต่อไป เราสร้าง themeReducer เพื่อจัดการกับสถานะของธีม

 import Colors from "./colors"; export const LIGHT = "LIGHT"; export const DARK = "DARK"; const themeReducer = (state, action) => { switch (action.type) { case LIGHT: return { ...Colors.light }; case DARK: return { ...Colors.dark }; default: return state; } }; export default themeReducer;

เช่นเดียวกับตัวลดทั้งหมด themeReducer ใช้สถานะและการดำเนินการ จากนั้นจะใช้คำสั่ง switch เพื่อกำหนดการดำเนินการปัจจุบัน หากเป็นประเภท LIGHT เราเพียงแค่กำหนดอุปกรณ์ประกอบฉาก Colors.light และหากเป็นประเภท DARK เราจะแสดงอุปกรณ์ประกอบฉาก Colors.dark เราสามารถทำสิ่งนี้ได้อย่างง่ายดายด้วย useState hook แต่เราเลือก useReducer เพื่อขับเคลื่อนจุดกลับบ้าน

เมื่อตั้งค่า themeReducer แล้ว เราก็สามารถรวมเข้ากับไฟล์ App.js ของเราได้

 import { useReducer, useState, useCallback } from "react"; import "./styles.css"; import todoReducer, { ADD_TODO } from "./todoReducer"; import TodoContext from "./todoContext"; import ThemeContext from "./themeContext"; import TodoList from "./TodoList"; import themeReducer, { DARK, LIGHT } from "./themeReducer"; import Colors from "./colors"; import ThemeToggler from "./ThemeToggler"; const themeSetter = useCallback( theme => themeDispatch({type: theme}, [themeDispatch]); export default function App() { const [id, setId] = useState(0); const [text, setText] = useState(""); const initialState = []; const [todoState, todoDispatch] = useReducer(todoReducer, initialState); const [themeState, themeDispatch] = useReducer(themeReducer, Colors.light); const themeSetter = useCallback( (theme) => { themeDispatch({ type: theme }); }, [themeDispatch] ); const addTodoItem = (e) => { e.preventDefault(); const newId = id + 1; setId(newId); todoDispatch({ type: ADD_TODO, id: newId, text: text }); setText(""); }; return ( <TodoContext.Provider value={[todoState, todoDispatch]}> <ThemeContext.Provider value={[ themeState, themeSetter ]} > <div className="app" style={{ ...themeState }}> <ThemeToggler /> <h1>Todo Example</h1> <form className="input" onSubmit={addTodoItem}> <input value={text} onChange={(e) => setText(e.target.value)} /> <button disabled={text.length === 0} type="submit"> + </button> </form> <TodoList /> </div> </ThemeContext.Provider> </TodoContext.Provider> ); }

ในโค้ดด้านบน เราได้เพิ่มบางสิ่งลงในแอปพลิเคชันสิ่งที่ต้องทำที่มีอยู่แล้วของเรา เราเริ่มต้นด้วยการนำเข้า ThemeContext , themeReducer , ThemeToggler และ Colors We created a reducer using the useReducer hook, passing the themeReducer and an initial value of Colors.light to it. This returned the themeState and themeDispatch to us.

We then nested our component with the provider function from the ThemeContext , passing the themeState and the dispatch functions to it. We also added theme styles to it by spreading out the themeStates . This works because the colors object already defined properties similar to what the JSX styles will accept.

However, the actual theme toggling happens in the ThemeToggler component. ลองมาดูที่มัน

 import ThemeContext from "./themeContext"; import { useContext, useState } from "react"; import { DARK, LIGHT } from "./themeReducer"; const ThemeToggler = () => { const [showLight, setShowLight] = useState(true); const [themeState, themeSetter] = useContext(ThemeContext); const dispatchDarkTheme = () => themeSetter(DARK); const dispatchLightTheme = () => themeSetter(LIGHT); const toggleTheme = () => { showLight ? dispatchDarkTheme() : dispatchLightTheme(); setShowLight(!showLight); }; console.log(themeState); return ( <div> <button onClick={toggleTheme}> {showLight ? "Change to Dark Theme" : "Change to Light Theme"} </button> </div> ); }; export default ThemeToggler;

In this component, we used the useContext hook to retrieve the values we passed to the ThemeContext.Provider from our App.js file. As shown above, these values include the ThemeState , dispatch function for the light theme, and dispatch function for the dark theme. Thereafter, we simply called the dispatch functions to toggle the themes. We also created a state showLight to determine the current theme. This allows us to easily change the button text depending on the current theme.

The useMemo Hook

The useMemo hook is designed to memoize expensive computations. Memoization simply means caching. It caches the computation result with respect to the dependency values so that when the same values are passed, useMemo will just spit out the already computed value without recomputing it again. This can significantly improve performance when done correctly.

The hook can be used as follows:

 const memoizedResult = useMemo(() => expensiveComputation(a, b), [a, b])

Let's consider three cases of the useMemo hook.

  1. When the dependency values, a and b remain the same.
    The useMemo hook will return the already computed memoized value without recomputation.
  2. When the dependency values, a and b change.
    The hook will recompute the value.
  3. When no dependency value is passed.
    The hook will recompute the value.

Let's take a look at an example to demonstrate this concept.

In the example below, we'll be computing the PAYE and Income after PAYE of a company's employees with fake data from JSONPlaceholder.

The calculation will be based on the personal income tax calculation procedure for Nigeria providers by PricewaterhouseCoopers available here.

This is shown in the sandbox below.

First, we queried the API to get the employees' data. We also get data for each employee (with respect to their employee id).

const [employee, setEmployee] = useState({}); const [employees, setEmployees] = useState([]); const [num, setNum] = useState(1); const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; useEffect(() => { const getEmployee = async () => { const { data } = await axios.get(`${endPoint}/${num}`); setEmployee(data); }; getEmployee(); }, [num]); useEffect(() => { axios.get(endPoint).then(({ data }) => setEmployees(data)); }, [num]);

เราใช้ axios และเมธอด async/await ใน useEffect แรก ตามด้วยจุด ตามด้วยไวยากรณ์ในอันที่สอง ทั้งสองวิธีทำงานในลักษณะเดียวกัน

ต่อไป โดยใช้ข้อมูลพนักงานที่เราได้รับจากด้านบน มาคำนวณตัวแปรบรรเทา:

 const taxVariablesCompute = useMemo(() => { const { income, noOfChildren, noOfDependentRelatives } = employee; //supposedly complex calculation //tax relief computations for relief Allowance, children relief, // relatives relief and pension relief const reliefs = reliefAllowance1 + reliefAllowance2 + childrenRelief + relativesRelief + pensionRelief; return reliefs; }, [employee]);

นี่เป็นการคำนวณที่ค่อนข้างซับซ้อน ดังนั้นเราจึงต้องรวมไว้ในเบ็ด useMemo เพื่อบันทึกหรือเพิ่มประสิทธิภาพ การจดบันทึกด้วยวิธีนี้จะทำให้มั่นใจได้ว่าการคำนวณจะไม่ถูกคำนวณใหม่หากเราพยายามเข้าถึงพนักงานคนเดิมอีกครั้ง

นอกจากนี้ โดยใช้ค่าการลดหย่อนภาษีที่ได้รับข้างต้น เราต้องการคำนวณ PAYE และรายได้หลัง PAYE

 const taxCalculation = useMemo(() => { const { income } = employee; let taxableIncome = income - taxVariablesCompute; let PAYE = 0; //supposedly complex calculation //computation to compute the PAYE based on the taxable income and tax endpoints const netIncome = income - PAYE; return { PAYE, netIncome }; }, [employee, taxVariablesCompute]);

เราทำการคำนวณภาษี (การคำนวณที่ค่อนข้างซับซ้อน) โดยใช้ตัวแปรภาษีที่คำนวณไว้ข้างต้นแล้วบันทึกด้วย hook useMemo

รหัสที่สมบูรณ์มีอยู่ที่นี่

เป็นไปตามขั้นตอนการคำนวณภาษีที่ให้ไว้ที่นี่ ขั้นแรก เราคำนวณการลดหย่อนภาษีโดยพิจารณาจากรายได้ จำนวนบุตร และจำนวนญาติในอุปการะ จากนั้น เราคูณรายได้ที่ต้องเสียภาษีด้วยอัตรา PIT เป็นขั้นตอน แม้ว่าการคำนวณที่เป็นปัญหาจะไม่จำเป็นสำหรับบทช่วยสอนนี้ทั้งหมด แต่ก็มีให้เพื่อแสดงให้เราเห็นว่าเหตุใด useMemo จึงมีความจำเป็น นี่เป็นการคำนวณที่ค่อนข้างซับซ้อน ดังนั้นเราจึงอาจจำเป็นต้องจดจำมันด้วย useMemo ดังที่แสดงไว้ด้านบน

หลังจากคำนวณค่าแล้ว เราก็แสดงผลลัพธ์ออกมา

สังเกตสิ่งต่อไปนี้เกี่ยวกับตะขอ useMemo

  • ควรใช้ useMemo เมื่อจำเป็นต้องปรับการคำนวณให้เหมาะสมเท่านั้น กล่าวอีกนัยหนึ่งเมื่อการคำนวณใหม่มีราคาแพง
  • ขอแนะนำให้เขียนการคำนวณโดยไม่ใช้การท่องจำก่อน และควรจำเฉพาะในกรณีที่ทำให้เกิดปัญหาด้านประสิทธิภาพเท่านั้น
  • การใช้ useMemo hook โดยไม่จำเป็นและไม่เกี่ยวข้องอาจทำให้ปัญหาด้านประสิทธิภาพเพิ่มขึ้น
  • บางครั้งการท่องจำมากเกินไปอาจทำให้เกิดปัญหาด้านประสิทธิภาพได้เช่นกัน

การใช้งาน useCallback Hook

useCallback มีจุดประสงค์เดียวกับ useMemo แต่จะคืนค่าการเรียกกลับที่บันทึกไว้แทนค่าที่บันทึก กล่าวอีกนัยหนึ่ง useCallback เหมือนกับการส่ง useMemo โดยไม่มีการเรียกใช้ฟังก์ชัน

ตัวอย่างเช่น พิจารณารหัสต่อไปนี้ด้านล่าง

 import React, {useCallback, useMemo} from 'react' const MemoizationExample = () => { const a = 5 const b = 7 const memoResult = useMemo(() => a + b, [a, b]) const callbackResult = useCallback(a + b, [a, b]) console.log(memoResult) console.log(callbackResult) return( <div> ... </div> ) } export default MemoizationExample

ในตัวอย่างข้างต้น ทั้ง memoResult และ callbackResult จะให้ค่า 12 เท่ากัน ที่นี่ useCallback จะคืนค่าที่บันทึกไว้ อย่างไรก็ตาม เราสามารถทำให้มันส่งคืนการเรียกกลับที่บันทึกโดยส่งผ่านเป็นฟังก์ชันได้

useCallback ด้านล่างจะส่งคืนการโทรกลับที่บันทึกไว้

 ... const callbackResult = useCallback(() => a + b, [a, b]) ...

จากนั้นเราสามารถทริกเกอร์การโทรกลับเมื่อมีการดำเนินการหรือในเบ็ด useEffect

 import {useCallback, useEffect} from 'react' const memoizationExample = () => { const a = 5 const b = 7 const callbackResult = useCallback(() => a + b, [a, b]) useEffect(() => { const callback = callbackResult() console.log(callback) }) return ( <div> <button onClick= {() => console.log(callbackResult())}> Trigger Callback </button> </div> ) } export default memoizationExample

ในโค้ดด้านบนนี้ เราได้กำหนดฟังก์ชันเรียกกลับโดยใช้เบ็ด useCallback จากนั้นเราเรียกการเรียกกลับใน useEffect hook เมื่อส่วนประกอบติดตั้งและเมื่อมีการคลิกปุ่ม

ทั้ง useEffect และการคลิกปุ่มให้ผลลัพธ์เหมือนกัน

โปรดทราบว่าแนวคิด สิ่งที่ควรทำ และไม่ควรทำกับ useMemo hook ยังใช้กับ useCallback hook ด้วย เราสามารถสร้างตัวอย่าง useMemo ขึ้นใหม่ด้วย useCallback

useRef

useRef ส่งคืนวัตถุที่สามารถคงอยู่ในแอปพลิเคชัน hook มีคุณสมบัติเพียงหนึ่งเดียว current และเราสามารถส่งอาร์กิวเมนต์ไปที่มันได้อย่างง่ายดาย

มันทำหน้าที่เดียวกันกับ createRef ที่ใช้ในส่วนประกอบตามคลาส เราสามารถสร้างการอ้างอิงด้วย hook นี้ได้ดังนี้:

 const newRef = useRef('')

ที่นี่เราสร้างผู้อ้างอิงใหม่ชื่อ newRef และส่งสตริงว่างไป

เบ็ดนี้ใช้เพื่อวัตถุประสงค์สองประการเป็นหลัก:

  1. การเข้าถึงหรือจัดการ DOM และ
  2. การจัดเก็บสถานะที่ไม่แน่นอน — สิ่งนี้มีประโยชน์เมื่อเราไม่ต้องการให้ส่วนประกอบแสดงผลใหม่เมื่อค่าเปลี่ยนแปลง

การจัดการ DOM

เมื่อส่งผ่านไปยังองค์ประกอบ DOM ออบเจ็กต์อ้างอิงจะชี้ไปที่องค์ประกอบนั้นและสามารถใช้เพื่อเข้าถึงแอตทริบิวต์และคุณสมบัติของ DOM

นี่เป็นตัวอย่างง่ายๆ ที่แสดงให้เห็นแนวคิดนี้

 import React, {useRef, useEffect} from 'react' const RefExample = () => { const headingRef = useRef('') console.log(headingRef) return( <div> <h1 className='topheading' ref={headingRef}>This is a h1 element</h1> </div> ) } export default RefExample

ในตัวอย่างข้างต้น เราได้กำหนด headingRef โดยใช้ตะขอ useRef ที่ส่งผ่านสตริงว่าง จากนั้นเราตั้งค่าผู้อ้างอิงในแท็ก h1 โดยผ่าน ref = {headingRef} ด้วยการตั้งค่าการอ้างอิงนี้ เราได้ขอให้ headingRef ชี้ไปที่องค์ประกอบ h1 ของเรา ซึ่งหมายความว่าเราสามารถเข้าถึงคุณสมบัติขององค์ประกอบ h1 ของเราได้จากการอ้างอิง

หากต้องการดูสิ่งนี้ หากเราตรวจสอบค่าของ console.log(headingRef) เราจะได้ {current: HTMLHeadingElement} หรือ {current: h1} และเราสามารถประเมินคุณสมบัติหรือแอตทริบิวต์ทั้งหมดขององค์ประกอบได้ สิ่งที่คล้ายกันนี้ใช้กับองค์ประกอบ HTML อื่นๆ

ตัวอย่างเช่น เราสามารถทำให้ข้อความเป็นตัวเอียงเมื่อส่วนประกอบติดตั้ง

 useEffect(() => { headingRef.current.style.font; }, []);

เรายังเปลี่ยนข้อความเป็นอย่างอื่นได้

 ... headingRef.current.innerHTML = "A Changed H1 Element"; ...

เรายังเปลี่ยนสีพื้นหลังของพาเรนต์คอนเทนเนอร์ได้ด้วย

 ... headingRef.current.parentNode.style.backgroundColor = "red"; ...

การจัดการ DOM แบบใดก็ได้สามารถทำได้ที่นี่ สังเกตว่า headingRef.current สามารถอ่านได้ในลักษณะเดียวกับ document.querySelector('.topheading')

กรณีการใช้งานที่น่าสนใจอย่างหนึ่งของ useRef hook ในการจัดการองค์ประกอบ DOM คือการโฟกัสเคอร์เซอร์ไปที่องค์ประกอบอินพุต รีบวิ่งผ่านมันไปกันเถอะ

 import {useRef, useEffect} from 'react' const inputRefExample = () => { const inputRef = useRef(null) useEffect(() => { inputRef.current.focus() }, []) return( <div> <input ref={inputRef} /> <button onClick = {() => inputRef.current.focus()}>Focus on Input </button> </div> ) } export default inputRefExample

ในโค้ดด้านบนนี้ เราได้สร้าง inputRef โดยใช้ hook ของ useRef แล้วขอให้ชี้ไปที่องค์ประกอบอินพุต จากนั้นเราทำให้เคอร์เซอร์โฟกัสไปที่การอ้างอิงอินพุตเมื่อส่วนประกอบโหลดและเมื่อคลิกปุ่มโดยใช้ inputRef.current.focus() สิ่งนี้เป็นไปได้เพราะ focus() เป็นแอตทริบิวต์ขององค์ประกอบอินพุต ดังนั้นผู้อ้างอิงจึงสามารถประเมินวิธีการได้

Refs ที่สร้างในองค์ประกอบหลักสามารถประเมินได้ที่องค์ประกอบย่อยโดยส่งต่อโดยใช้ React.forwardRef() ลองมาดูที่มัน

ขั้นแรกให้สร้างส่วนประกอบอื่น NewInput.js และเพิ่มรหัสต่อไปนี้ลงไป

 import { useRef, forwardRef } from "react"; const NewInput = forwardRef((props, ref) => { return <input placeholder={props.val} ref={ref} />; }); export default NewInput;

ส่วนประกอบนี้ยอมรับ props และการ ref เราส่งการอ้างอิงไปยัง ref prop และ props.val ไปยัง placeholder prop ส่วนประกอบ React ปกติไม่ใช้แอตทริบิวต์ ref คุณลักษณะนี้จะใช้ได้เฉพาะเมื่อเราห่อด้วย React.forwardRef ดังที่แสดงด้านบน

จากนั้นเราสามารถเรียกสิ่งนี้ในองค์ประกอบหลักได้อย่างง่ายดาย

 ... <NewInput val="Just an example" ref={inputRef} /> ...

การจัดเก็บสถานะที่ไม่แน่นอน

การอ้างอิงไม่ได้ใช้เพื่อจัดการองค์ประกอบ DOM เท่านั้น แต่ยังใช้เพื่อเก็บค่าที่ไม่แน่นอนโดยไม่ต้องแสดงองค์ประกอบทั้งหมดอีกครั้ง

ตัวอย่างต่อไปนี้จะตรวจจับจำนวนครั้งที่มีการคลิกปุ่มโดยไม่แสดงส่วนประกอบซ้ำ

 import { useRef } from "react"; export default function App() { const countRef = useRef(0); const increment = () => { countRef.current++; console.log(countRef); }; return ( <div className="App"> <button onClick={increment}>Increment </button> </div> ); }

ในโค้ดด้านบน เราได้เพิ่ม countRef เมื่อมีการคลิกปุ่ม จากนั้นจึงบันทึกลงในคอนโซล แม้ว่าค่าจะเพิ่มขึ้นตามที่แสดงในคอนโซล เราจะไม่เห็นการเปลี่ยนแปลงใดๆ หากเราพยายามประเมินโดยตรงในคอมโพเนนต์ของเรา มันจะอัปเดตในองค์ประกอบเมื่อแสดงผลอีกครั้งเท่านั้น

โปรดทราบว่าในขณะที่ useState เป็นแบบอะซิงโครนัส useRef เป็นแบบซิงโครนัส กล่าวคือ ค่าจะพร้อมใช้งานทันทีหลังจากอัปเดต

ตะขอ useLayoutEffect

เช่นเดียวกับตะขอ useEffect useLayoutEffect จะถูกเรียกหลังจากติดตั้งและแสดงผลส่วนประกอบ hook นี้เริ่มทำงานหลังจากการกลายพันธุ์ของ DOM และทำแบบซิงโครนัส นอกเหนือจากการเรียกแบบซิงโครนัสหลังจากการกลายพันธุ์ DOM แล้ว useLayoutEffect จะทำสิ่งเดียวกันกับ useEffect

useLayoutEffect ควรใช้สำหรับทำการกลายพันธุ์ DOM หรือการวัดที่เกี่ยวข้องกับ DOM เท่านั้น มิฉะนั้น คุณควรใช้ useEffect hook การใช้เบ็ด useEffect สำหรับฟังก์ชันการกลายพันธุ์ของ DOM อาจทำให้เกิดปัญหาด้านประสิทธิภาพบางอย่าง เช่น การกะพริบ แต่ useLayoutEffect จัดการได้อย่างสมบูรณ์แบบในขณะที่ทำงานหลังจากการกลายพันธุ์เกิดขึ้น

ลองมาดูตัวอย่างบางส่วนเพื่อสาธิตแนวคิดนี้

  1. เราจะได้ความกว้างและความสูงของหน้าต่างเมื่อปรับขนาด
 import {useState, useLayoutEffect} from 'react' const ResizeExample = () =>{ const [windowSize, setWindowSize] = useState({width: 0, height: 0}) useLayoutEffect(() => { const resizeWindow = () => setWindowSize({ width: window.innerWidth, height: window.innerHeight }) window.addEventListener('resize', resizeWindow) return () => window.removeEventListener('resize', resizeWindow) }, []) return ( <div> <p>width: {windowSize.width}</p> <p>height: {windowSize.height}</p> </div> ) } export default ResizeExample

ในโค้ดด้านบนนี้ เราได้สร้าง state windowSize ที่มีคุณสมบัติ width และ height จากนั้นเราตั้งค่าสถานะเป็นความกว้างและความสูงของหน้าต่างปัจจุบันตามลำดับเมื่อปรับขนาดหน้าต่าง เรายังทำความสะอาดโค้ดเมื่อยกเลิกการต่อเชื่อม กระบวนการล้างข้อมูลเป็นสิ่งสำคัญใน useLayoutEffect เพื่อล้างการจัดการ DOM และปรับปรุงประสิทธิภาพ

  1. มาเบลอข้อความด้วย useLayoutEffect
 import { useRef, useState, useLayoutEffect } from "react"; export default function App() { const paragraphRef = useRef(""); useLayoutEffect(() => { const { current } = paragraphRef; const blurredEffect = () => { current.style.color = "transparent"; current.style.textShadow = "0 0 5px rgba(0,0,0,0.5)"; }; current.addEventListener("click", blurredEffect); return () => current.removeEventListener("click", blurredEffect); }, []); return ( <div className="App"> <p ref={paragraphRef}>This is the text to blur</p> </div> ); }

เราใช้ useRef และ useLayoutEffect ร่วมกันในโค้ดด้านบน ก่อนอื่นเราสร้างการอ้างอิง paragraphRef เพื่อชี้ไปที่ย่อหน้าของเรา จากนั้นเราสร้างตัวฟังเหตุการณ์เมื่อคลิกเพื่อตรวจสอบเมื่อมีการคลิกย่อหน้าแล้วเบลอโดยใช้คุณสมบัติสไตล์ที่เรากำหนด สุดท้าย เราล้าง event listener โดยใช้ removeEventListener

การ useDispatch และ useSelector Hooks

useDispatch เป็น Redux hook สำหรับการสั่งงาน (ทริกเกอร์) ในแอปพลิเคชัน ใช้วัตถุการกระทำเป็นอาร์กิวเมนต์และเรียกใช้การดำเนินการ useDispatch คือความเท่าเทียมกันของ hook กับ mapDispatchToProps

ในทางกลับกัน useSelector เป็น Redux hook สำหรับประเมินสถานะ Redux ต้องใช้ฟังก์ชันเพื่อเลือก Redux reducer ที่แน่นอนจากสโตร์แล้วส่งคืนสถานะที่เกี่ยวข้อง

เมื่อร้าน Redux ของเราเชื่อมต่อกับแอปพลิเคชัน React ผ่านผู้ให้บริการ Redux เราสามารถเรียกใช้การดำเนินการด้วย useDispatch และเข้าถึงสถานะด้วย useSelector ทุกการกระทำและสถานะของ Redux สามารถประเมินได้ด้วยตะขอสองตัวนี้

โปรดทราบว่าสถานะเหล่านี้มาพร้อมกับ React Redux (แพ็คเกจที่ทำให้การประเมินที่จัดเก็บ Redux ทำได้ง่ายในแอปพลิเคชัน React) ไม่มีอยู่ในไลบรารี Redux หลัก

ตะขอเหล่านี้ใช้งานง่ายมาก ขั้นแรก เราต้องประกาศฟังก์ชันการสั่งงานแล้วเรียกทำงาน

 import {useDispatch, useSelector} from 'react-redux' import {useEffect} from 'react' const myaction from '...' const ReduxHooksExample = () =>{ const dispatch = useDispatch() useEffect(() => { dispatch(myaction()); //alternatively, we can do this dispatch({type: 'MY_ACTION_TYPE'}) }, []) const mystate = useSelector(state => state.myReducerstate) return( ... ) } export default ReduxHooksExample

ในโค้ดด้านบนนี้ เรานำเข้า useDispatch และ useSelector จาก react-redux จากนั้นใน useEffect hook เราได้ส่งการดำเนินการ เราสามารถกำหนดการกระทำในไฟล์อื่นแล้วเรียกมันที่นี่หรือเราสามารถกำหนดได้โดยตรงตามที่แสดงในการโทร useEffect

เมื่อเราส่งการดำเนินการแล้ว รัฐของเราจะพร้อมใช้งาน จากนั้นเราสามารถดึงสถานะโดยใช้ useSelector hook ดังที่แสดง สามารถใช้ state ในลักษณะเดียวกับที่เราจะใช้ state จาก useState hook

ลองมาดูตัวอย่างเพื่อสาธิตตะขอสองตัวนี้

เพื่อแสดงแนวคิดนี้ เราต้องสร้าง Redux store, reducer และ actions เพื่อลดความซับซ้อนของสิ่งต่าง ๆ ที่นี่ เราจะใช้ไลบรารี Redux Toolkit กับฐานข้อมูลปลอมของเราจาก JSONPlaceholder

เราจำเป็นต้องติดตั้งแพ็คเกจต่อไปนี้เพื่อเริ่มต้น รันคำสั่ง bash ต่อไปนี้

 npm i redux @reduxjs/toolkit react-redux axios

ขั้นแรก ให้สร้าง employeesSlice.js เพื่อจัดการกับตัวลดและการดำเนินการสำหรับ API ของพนักงานของเรา

 import { createAsyncThunk, createSlice } from "@reduxjs/toolkit"; import axios from "axios"; const endPoint = "https://my-json-server.typicode.com/ifeanyidike/jsondata/employees"; export const fetchEmployees = createAsyncThunk("employees/fetchAll", async () => { const { data } = await axios.get(endPoint); return data; }); const employeesSlice = createSlice({ name: "employees", initialState: { employees: [], loading: false, error: "" }, reducers: {}, extraReducers: { [fetchEmployees.pending]: (state, action) => { state.status = "loading"; }, [fetchEmployees.fulfilled]: (state, action) => { state.status = "success"; state.employees = action.payload; }, [fetchEmployees.rejected]: (state, action) => { state.status = "error"; state.error = action.error.message; } } }); export default employeesSlice.reducer;

นี่คือการตั้งค่ามาตรฐานสำหรับชุดเครื่องมือ Redux เราใช้ createAsyncThunk เพื่อเข้าถึงมิดเดิลแวร์ Thunk เพื่อดำเนินการ async ซึ่งช่วยให้เราดึงรายชื่อพนักงานจาก API ได้ จากนั้นเราสร้าง employeesSlice และส่งคืน "กำลังโหลด" "ข้อผิดพลาด" และข้อมูลของพนักงานขึ้นอยู่กับประเภทการดำเนินการ

ชุดเครื่องมือ Redux ยังทำให้การตั้งค่าร้านค้าเป็นเรื่องง่าย ที่นี่คือร้าน

 import { configureStore } from "@reduxjs/toolkit"; import { combineReducers } from "redux"; import employeesReducer from "./employeesSlice"; const reducer = combineReducers({ employees: employeesReducer }); export default configureStore({ reducer });;

ในที่นี้ เราใช้ combineReducers เพื่อรวมกลุ่มตัวลดขนาดและฟังก์ชัน configureStore ที่จัดเตรียมโดยชุดเครื่องมือ Redux เพื่อตั้งค่าที่จัดเก็บ

ลองใช้สิ่งนี้ในแอปพลิเคชันของเราต่อไป

ขั้นแรก เราต้องเชื่อมต่อ Redux กับแอปพลิเคชัน React ของเรา ตามหลักการแล้ว ควรทำที่รูทของแอปพลิเคชันของเรา ฉันชอบที่จะทำในไฟล์ index.js

 import React, { StrictMode } from "react"; import ReactDOM from "react-dom"; import store from "./redux/store"; import { Provider } from "react-redux"; import App from "./App"; const rootElement = document.getElementById("root"); ReactDOM.render( <Provider store={store}> <StrictMode> <App /> </StrictMode> </Provider>, rootElement );

ที่นี่ฉันได้นำเข้าร้านค้าที่ฉันสร้างขึ้นด้านบนและรวมถึง Provider จาก react-redux

จากนั้นฉันก็รวมแอปพลิเคชันทั้งหมดด้วยฟังก์ชัน Provider แล้วส่งร้านค้าไปให้ ทำให้ร้านค้าสามารถเข้าถึงได้ตลอดแอปพลิเคชันของเรา

จากนั้นเราสามารถดำเนินการใช้ useDispatch และ useSelector hooks เพื่อดึงข้อมูล

ลองทำสิ่งนี้ในไฟล์ App.js ของเรา

 import { useDispatch, useSelector } from "react-redux"; import { fetchEmployees } from "./redux/employeesSlice"; import { useEffect } from "react"; export default function App() { const dispatch = useDispatch(); useEffect(() => { dispatch(fetchEmployees()); }, [dispatch]); const employeesState = useSelector((state) => state.employees); const { employees, loading, error } = employeesState; return ( <div className="App"> {loading ? ( "Loading..." ) : error ? ( <div>{error}</div> ) : ( <> <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <h3>{`${employee.firstName} ${employee.lastName}`}</h3> </div> ))} </> )} </div> ); }

ในโค้ดด้านบนนี้ เราใช้ useDispatch hook เพื่อเรียกใช้การดำเนินการ fetchEmployees ที่สร้างขึ้นในไฟล์ employeesSlice.js สิ่งนี้ทำให้พนักงานระบุว่ามีอยู่ในใบสมัครของเรา จากนั้นเราใช้ useSelector hook เพื่อรับสถานะ หลังจากนั้น เราแสดงผลโดยการทำแผนที่ผ่าน employees

useHistory การใช้งาน

การนำทางมีความสำคัญมากในแอปพลิเคชัน React ในขณะที่คุณสามารถทำสิ่งนี้ได้สองสามวิธี React Router ให้วิธีที่ง่าย มีประสิทธิภาพ และเป็นที่นิยมในการบรรลุการกำหนดเส้นทางแบบไดนามิกในแอปพลิเคชัน React นอกจากนี้ React Router ยังมีตะขอสองสามตัวสำหรับการประเมินสถานะของเราเตอร์และการนำทางบนเบราว์เซอร์ แต่หากต้องการใช้งาน คุณต้องตั้งค่าแอปพลิเคชันของคุณอย่างถูกต้องก่อน

ในการใช้ React Router hook อันดับแรก เราควรห่อแอปพลิเคชันของเราด้วย BrowserRouter จากนั้นเราสามารถซ้อนเส้นทางด้วย Switch และ Route

แต่ก่อนอื่นเราต้องติดตั้งแพ็คเกจโดยใช้คำสั่งต่อไปนี้

 npm install react-router-dom

จากนั้นเราต้องตั้งค่าแอปพลิเคชันของเราดังนี้ ฉันชอบทำสิ่งนี้ในไฟล์ App.js ของฉัน

 import { BrowserRouter as Router, Switch, Route } from "react-router-dom"; import Employees from "./components/Employees"; export default function App() { return ( <div className="App"> <Router> <Switch> <Route path='/'> <Employees /> </Route> ... </Switch> </Router> </div> ); }

เราสามารถมีเส้นทางได้มากที่สุดเท่าที่เป็นไปได้ขึ้นอยู่กับจำนวนองค์ประกอบที่เราต้องการแสดง ที่นี่เราได้แสดงเฉพาะองค์ประกอบ Employees แอตทริบิวต์ path บอก React Router DOM ถึงเส้นทางของส่วนประกอบ และสามารถประเมินได้ด้วยสตริงการสืบค้นหรือวิธีอื่นๆ

คำสั่งซื้อมีความสำคัญที่นี่ ควรวางเส้นทางหลักไว้ใต้เส้นทางย่อยเป็นต้น หากต้องการลบล้างลำดับนี้ คุณต้องใส่คีย์เวิร์ด exact ตรงกันทั้งหมดบนเส้นทางรูท

 <Route path='/' exact > <Employees /> </Route>

ตอนนี้เราได้ตั้งค่าเราเตอร์แล้ว เราก็สามารถใช้ useHistory hook และขอเกี่ยว React Router อื่นๆ ในแอปพลิเคชันของเราได้

ในการใช้เบ็ด useHistory เราต้องประกาศก่อนดังนี้

 import {useHistory} from 'history' import {useHistory} from 'react-router-dom' const Employees = () =>{ const history = useHistory() ... }

หากเราบันทึกประวัติไปที่คอนโซล เราจะเห็นคุณสมบัติหลายอย่างที่เกี่ยวข้องกัน ซึ่งรวมถึง block , createHref , go , goBack , goForward , length , listen , location , push , replace แม้ว่าคุณสมบัติทั้งหมดเหล่านี้จะมีประโยชน์ แต่คุณมักจะใช้ history.push และ history.replace บ่อยกว่าคุณสมบัติอื่นๆ

ลองใช้คุณสมบัตินี้เพื่อย้ายจากหน้าหนึ่งไปอีกหน้าหนึ่ง

สมมติว่าเราต้องการดึงข้อมูลเกี่ยวกับพนักงานคนหนึ่งเมื่อเราคลิกที่ชื่อของพวกเขา เราสามารถใช้เบ็ด useHistory เพื่อนำทางไปยังหน้าใหม่ที่จะแสดงข้อมูลของพนักงาน

 function moveToPage = (id) =>{ history.push(`/employees/${id}`) }

เราสามารถนำสิ่งนี้ไปใช้ในไฟล์ Employee.js โดยเพิ่มสิ่งต่อไปนี้

 import { useEffect } from "react"; import { Link, useHistory, useLocation } from "react-router-dom"; export default function Employees() { const history = useHistory(); function pushToPage = (id) => { history.push(`/employees/${id}`) } ... return ( <div> ... <h1>List of Employees</h1> {employees.map((employee) => ( <div key={employee.id}> <span>{`${employee.firstName} ${employee.lastName} `}</span> <button onClick={pushToPage(employee.id)}> » </button> </div> ))} </div> ); }

ในฟังก์ชัน pushToPage เราใช้ history จากตะขอ useHistory เพื่อนำทางไปยังหน้าของพนักงานและส่งรหัสพนักงานควบคู่ไปกับ

useLocation การใช้งาน

เบ็ดนี้ยังมาพร้อมกับ React Router DOM เป็น hook ที่นิยมใช้ในการทำงานกับพารามิเตอร์สตริงการสืบค้น เบ็ดนี้คล้ายกับ window.location ในเบราว์เซอร์

 import {useLocation} from 'react' const LocationExample = () =>{ const location = useLocation() return ( ... ) } export default LocationExample

ตะขอ useLocation ส่งคืน pathname พารามิเตอร์ search hash และ state พารามิเตอร์ที่ใช้บ่อยที่สุด ได้แก่ pathname และ search แต่คุณสามารถใช้ hash ได้เท่าๆ กัน และ state จำนวนมากในแอปพลิเคชันของคุณ

คุณสมบัติ pathname ของตำแหน่งจะส่งคืนเส้นทางที่เราตั้งค่าไว้ในการตั้งค่า Route ของเรา ในขณะที่ search จะส่งคืนพารามิเตอร์การค้นหาข้อความค้นหาถ้ามี ตัวอย่างเช่น หากเราส่ง 'http://mywebsite.com/employee/?id=1' ไปยังข้อความค้นหาของเรา pathname จะเป็น /employee และการ search จะเป็น ?id=1

จากนั้นเราสามารถดึงพารามิเตอร์การค้นหาต่างๆ โดยใช้แพ็คเกจ เช่น สตริงการสืบค้น หรือโดยการเข้ารหัส

การใช้งาน useParams Hook

หากเราตั้งค่าเส้นทางด้วยพารามิเตอร์ URL ในแอตทริบิวต์เส้นทาง เราสามารถประเมินพารามิเตอร์เหล่านั้นเป็นคู่คีย์/ค่าด้วย hook useParams

ตัวอย่างเช่น สมมติว่าเรามีเส้นทางต่อไปนี้

 <Route path='/employees/:id' > <Employees /> </Route>

เส้นทางจะคาดหวัง id แบบไดนามิกแทนที่ :id

ด้วยเบ็ด useParams เราสามารถประเมิน id ที่ส่งโดยผู้ใช้ หากมี

ตัวอย่างเช่น สมมติว่าผู้ใช้ส่งฟังก์ชันต่อไปนี้ด้วย history.push

 function goToPage = () => { history.push(`/employee/3`) }

เราสามารถใช้ useParams hook เพื่อเข้าถึงพารามิเตอร์ URL นี้ได้ดังนี้

 import {useParams} from 'react-router-dom' const ParamsExample = () =>{ const params = useParams() console.log(params) return( <div> ... </div> ) } export default ParamsExample

หากเราบันทึก params ลงในคอนโซล เราจะได้วัตถุต่อไปนี้ {id: "3"}

useRouteMatch Hook

เบ็ดนี้ให้การเข้าถึงวัตถุที่ตรงกัน ส่งคืนการจับคู่ที่ใกล้เคียงที่สุดกับส่วนประกอบหากไม่มีอาร์กิวเมนต์

วัตถุที่ตรงกันจะส่งคืนพารามิเตอร์หลายตัวรวมถึง path (เหมือนกับเส้นทางที่ระบุในเส้นทาง) URL วัตถุ params และ isExact

ตัวอย่างเช่น เราสามารถใช้ useRouteMatch เพื่อส่งคืนส่วนประกอบตามเส้นทาง

 import { useRouteMatch } from "react-router-dom"; import Employees from "..."; import Admin from "..." const CustomRoute = () => { const match = useRouteMatch("/employees/:id"); return match ? ( <Employee /> ) : ( <Admin /> ); }; export default CustomRoute;

ในโค้ดด้านบนนี้ เราตั้งค่าพาธของเส้นทางด้วย useRouteMatch แล้วเรนเดอร์องค์ประกอบ <Employee /> หรือ <Admin /> ขึ้นอยู่กับเส้นทางที่ผู้ใช้เลือก

เพื่อให้ใช้งานได้ เรายังจำเป็นต้องเพิ่มเส้นทางไปยังไฟล์ App.js ของเรา

 ... <Route> <CustomRoute /> </Route> ...

การสร้างตะขอแบบกำหนดเอง

ตามเอกสารของ React การสร้าง hook แบบกำหนดเองช่วยให้เราสามารถแยกตรรกะออกเป็นฟังก์ชันที่ใช้ซ้ำได้ อย่างไรก็ตาม คุณต้องแน่ใจว่ากฎทั้งหมดที่ใช้กับ React hooks นั้นใช้กับ hook ที่คุณกำหนดเอง ตรวจสอบกฎของ React hook ที่ด้านบนของบทช่วยสอนนี้ และตรวจสอบให้แน่ใจว่า hook ที่คุณกำหนดเองสอดคล้องกับแต่ละกฎ

Custom hook ช่วยให้เราเขียนฟังก์ชันได้เพียงครั้งเดียวและนำมาใช้ใหม่ได้ทุกเมื่อที่จำเป็น และด้วยเหตุนี้จึงเป็นไปตามหลักการ DRY

ตัวอย่างเช่น เราสามารถสร้าง hook แบบกำหนดเองเพื่อรับตำแหน่งการเลื่อนบนหน้าของเราได้ดังนี้

 import { useLayoutEffect, useState } from "react"; export const useScrollPos = () => { const [scrollPos, setScrollPos] = useState({ x: 0, y: 0 }); useLayoutEffect(() => { const getScrollPos = () => setScrollPos({ x: window.pageXOffset, y: window.pageYOffset }); window.addEventListener("scroll", getScrollPos); return () => window.removeEventListener("scroll", getScrollPos); }, []); return scrollPos; };

ที่นี่ เรากำหนด hook แบบกำหนดเองเพื่อกำหนดตำแหน่งการเลื่อนบนหน้า เพื่อให้บรรลุสิ่งนี้ ก่อนอื่นเราได้สร้างสถานะ scrollPos เพื่อเก็บตำแหน่งการเลื่อน เนื่องจากสิ่งนี้จะเป็นการแก้ไข DOM เราจึงต้องใช้ useLayoutEffect แทน useEffect เราได้เพิ่มตัวฟังเหตุการณ์การเลื่อนเพื่อจับตำแหน่งการเลื่อน x และ y จากนั้นจึงล้างตัวฟังเหตุการณ์ ในที่สุด เราก็กลับมาที่ตำแหน่งเลื่อน

เราสามารถใช้ hook แบบกำหนดเองนี้ได้ทุกที่ในแอปพลิเคชันของเราโดยเรียกใช้และใช้งานเหมือนกับที่เราจะใช้สถานะอื่น

 import {useScrollPos} from './Scroll' const App = () =>{ const scrollPos = useScrollPos() console.log(scrollPos.x, scrollPos.y) return ( ... ) } export default App

ที่นี่เรานำเข้า useScrollPos ของ hook แบบกำหนดเองที่เราสร้างขึ้นด้านบน จากนั้นเราเริ่มต้นและบันทึกค่าลงในคอนโซลของเรา หากเราเลื่อนบนหน้า เบ็ดจะแสดงตำแหน่งการเลื่อนให้เราเห็นในทุกขั้นตอนของการเลื่อน

เราสามารถสร้าง hook แบบกำหนดเองเพื่อทำอะไรก็ได้ที่เราจินตนาการได้ในแอปของเรา อย่างที่คุณเห็น เราต้องใช้ inbuilt React hook เพื่อทำหน้าที่บางอย่าง นอกจากนี้เรายังสามารถใช้ไลบรารีของบุคคลที่สามเพื่อสร้าง hook ที่กำหนดเองได้ แต่ถ้าเราทำเช่นนั้น เราจะต้องติดตั้งไลบรารีนั้นเพื่อให้สามารถใช้ hook ได้

บทสรุป

ในบทช่วยสอนนี้ เราได้ศึกษาเกี่ยวกับ React hook ที่มีประโยชน์ที่คุณจะใช้ในแอปพลิเคชันส่วนใหญ่ของคุณ เราตรวจสอบสิ่งที่นำเสนอและวิธีใช้งานในแอปพลิเคชันของคุณ นอกจากนี้เรายังดูตัวอย่างโค้ดต่างๆ เพื่อช่วยให้คุณเข้าใจ hooks เหล่านี้และนำไปใช้กับแอปพลิเคชันของคุณ

ฉันแนะนำให้คุณลองใช้ hooks เหล่านี้ในแอปพลิเคชันของคุณเองเพื่อทำความเข้าใจเพิ่มเติม

แหล่งข้อมูลจาก React Docs

  • ตะขอคำถามที่พบบ่อย
  • Redux Toolkit
  • ใช้ State Hook
  • การใช้เอฟเฟกต์ตะขอ
  • ขออ้างอิง API
  • React Redux Hooks
  • React Router Hooks