React Hooks ที่มีประโยชน์ซึ่งคุณสามารถใช้ในโครงการของคุณ
เผยแพร่แล้ว: 2022-03-10Hooks เป็นฟังก์ชันที่ให้คุณ เชื่อม ต่อหรือ ใช้งาน ฟีเจอร์ 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 ต่างๆ การพิจารณาอนุสัญญาและกฎเกณฑ์ที่ใช้กับพวกเขาอาจเป็นประโยชน์ นี่คือกฎเกณฑ์บางประการที่ใช้กับตะขอ
- หลักการตั้งชื่อของ hooks ควรเริ่มต้นด้วยการ
use
คำนำหน้า ดังนั้น เราสามารถมีuseState
,useEffect
เป็นต้น หากคุณใช้โปรแกรมแก้ไขโค้ดสมัยใหม่ เช่น Atom และ VSCode ปลั๊กอิน ESLint อาจเป็นคุณสมบัติที่มีประโยชน์มากสำหรับ React hooks ปลั๊กอินมีคำเตือนและคำแนะนำที่เป็นประโยชน์เกี่ยวกับแนวทางปฏิบัติที่ดีที่สุด - ต้องเรียกใช้ hook ที่ระดับบนสุดของส่วนประกอบ ก่อนคำสั่ง return ไม่สามารถเรียกได้ภายในคำสั่งแบบมีเงื่อนไข ลูป หรือฟังก์ชันที่ซ้อนกัน
- ต้องเรียก 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
ด้านบนจะทำงานเมื่อตรงตามเงื่อนไขสองข้อนี้
- บนเมานต์ — หลังจากสร้างองค์ประกอบแล้ว
- เมื่อมูลค่าการ
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.
- When the dependency values, a and b remain the same.
TheuseMemo
hook will return the already computed memoized value without recomputation. - When the dependency values, a and b change.
The hook will recompute the value. - 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
และส่งสตริงว่างไป
เบ็ดนี้ใช้เพื่อวัตถุประสงค์สองประการเป็นหลัก:
- การเข้าถึงหรือจัดการ DOM และ
- การจัดเก็บสถานะที่ไม่แน่นอน — สิ่งนี้มีประโยชน์เมื่อเราไม่ต้องการให้ส่วนประกอบแสดงผลใหม่เมื่อค่าเปลี่ยนแปลง
การจัดการ 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
จัดการได้อย่างสมบูรณ์แบบในขณะที่ทำงานหลังจากการกลายพันธุ์เกิดขึ้น
ลองมาดูตัวอย่างบางส่วนเพื่อสาธิตแนวคิดนี้
- เราจะได้ความกว้างและความสูงของหน้าต่างเมื่อปรับขนาด
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 และปรับปรุงประสิทธิภาพ
- มาเบลอข้อความด้วย
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