เรียนรู้วิธีเชื่อง React's useCallback Hook
เผยแพร่แล้ว: 2022-04-27ไม่เป็นความลับที่ React.js ได้รับความนิยมอย่างกว้างขวางในช่วงไม่กี่ปีที่ผ่านมา ตอนนี้เป็นไลบรารี JavaScript ที่เหมาะสำหรับผู้เล่นที่โดดเด่นที่สุดในอินเทอร์เน็ตหลายคน รวมทั้ง Facebook และ WhatsApp
สาเหตุหลักประการหนึ่งของการเพิ่มขึ้นคือการเปิดตัว hooks ในเวอร์ชัน 16.8 React hooks ช่วยให้คุณสามารถใช้งานฟังก์ชัน React ได้โดยไม่ต้องเขียนส่วนประกอบของคลาส ตอนนี้ส่วนประกอบที่ใช้งานได้กับ hooks ได้กลายเป็นโครงสร้างที่นักพัฒนาใช้งาน React ไปแล้ว
ในบล็อกโพสต์นี้ เราจะเจาะลึกลงไปในเบ็ดเฉพาะ — useCallback
— เพราะเกี่ยวข้องกับส่วนพื้นฐานของการเขียนโปรแกรมเชิงฟังก์ชันที่เรียกว่าการท่องจำ คุณจะทราบอย่างชัดเจนว่าควรใช้งาน useCallback
hook อย่างไรและเมื่อใด และใช้ความสามารถในการเพิ่มประสิทธิภาพให้เกิดประโยชน์สูงสุด
พร้อม? มาดำน้ำกันเถอะ!
Memoization คืออะไร?
Memoization คือเมื่อฟังก์ชันที่ซับซ้อนเก็บเอาท์พุตของมันเอาไว้ ดังนั้นในครั้งต่อไปจะมีการเรียกฟังก์ชันนี้ด้วยอินพุตเดียวกัน คล้ายกับการแคช แต่ในระดับท้องถิ่นมากกว่า มันสามารถข้ามการคำนวณที่ซับซ้อนและส่งคืนผลลัพธ์ได้เร็วขึ้นตามที่คำนวณแล้ว
สิ่งนี้สามารถมีผลกระทบอย่างมีนัยสำคัญต่อการจัดสรรหน่วยความจำและประสิทธิภาพ และความเครียดนั้นคือสิ่งที่ useCallback
hook มีไว้เพื่อบรรเทา
useCallback ของ React เทียบกับ useMemo
ณ จุดนี้ เป็นมูลค่าการกล่าวขวัญว่า useCallback
จับคู่อย่างดีกับ hook อื่นที่เรียกว่า useMemo
เราจะพูดถึงทั้งคู่ แต่ในส่วนนี้ เราจะเน้นที่ useCallback
เป็นหัวข้อหลัก
ข้อแตกต่างที่สำคัญคือ useMemo
จะคืนค่าที่บันทึกไว้ ในขณะที่ useCallback
จะคืนค่าฟังก์ชันที่บันทึก นั่นหมายความว่า useMemo
ใช้สำหรับเก็บค่าที่คำนวณไว้ ในขณะที่ useCallback
จะส่งคืนฟังก์ชันที่คุณสามารถเรียกใช้ได้ในภายหลัง
hooks เหล่านี้จะทำให้คุณได้รับเวอร์ชันแคชกลับคืนมา เว้นแต่ว่าการขึ้นต่อกันอย่างใดอย่างหนึ่ง (เช่น สถานะหรืออุปกรณ์ประกอบฉาก) จะเปลี่ยนแปลง
มาดูการทำงานของทั้งสองฟังก์ชั่นกัน:
import { useMemo, useCallback } from 'react' const values = [3, 9, 6, 4, 2, 1] // This will always return the same value, a sorted array. Once the values array changes then this will recompute. const memoizedValue = useMemo(() => values.sort(), [values]) // This will give me back a function that can be called later on. It will always return the same result unless the values array is modified. const memoizedFunction = useCallback(() => values.sort(), [values])
ข้อมูลโค้ดด้านบนเป็นตัวอย่างที่ประดิษฐ์ขึ้น แต่แสดงความแตกต่างระหว่างสองคอลแบ็ก:
-
memoizedValue
จะกลายเป็นอาร์เรย์[1, 2, 3, 4, 6, 9]
ตราบใดที่ตัวแปรค่ายังคงอยู่memoizedValue
จะบันทึก และจะไม่มีวันคำนวณใหม่ -
memoizedFunction
จะเป็นฟังก์ชันที่จะคืนค่าอาร์เรย์[1, 2, 3, 4, 6, 9]
สิ่งที่ยอดเยี่ยมเกี่ยวกับการเรียกกลับทั้งสองนี้คือพวกเขาจะถูกแคชและรอจนกว่าอาร์เรย์การขึ้นต่อกันจะเปลี่ยนไป ซึ่งหมายความว่าในการแสดงผล พวกเขาจะไม่ได้รับขยะที่เก็บรวบรวม
การแสดงผลและการตอบสนอง
เหตุใดการท่องจำจึงมีความสำคัญเมื่อพูดถึง React
มันเกี่ยวข้องกับวิธีที่ React แสดงส่วนประกอบของคุณ React ใช้ Virtual DOM ที่เก็บไว้ในหน่วยความจำเพื่อเปรียบเทียบข้อมูลและตัดสินใจว่าจะอัปเดตอะไร
DOM เสมือนช่วยตอบสนองด้วยประสิทธิภาพและทำให้แอปพลิเคชันของคุณรวดเร็ว โดยค่าเริ่มต้น หากค่าใดๆ ในองค์ประกอบของคุณเปลี่ยนแปลง ส่วนประกอบทั้งหมดจะแสดงใหม่ สิ่งนี้ทำให้ React “ตอบสนอง” กับอินพุตของผู้ใช้และอนุญาตให้หน้าจออัปเดตโดยไม่ต้องโหลดหน้าซ้ำ
คุณไม่ต้องการแสดงองค์ประกอบของคุณเนื่องจากการเปลี่ยนแปลงจะไม่ส่งผลกระทบต่อองค์ประกอบนั้น นี่คือจุดที่การท่องจำผ่าน useCallback
และ useMemo
มีประโยชน์
เมื่อ React แสดงส่วนประกอบของคุณอีกครั้ง มันจะสร้างฟังก์ชันที่คุณได้ประกาศไว้ภายในส่วนประกอบของคุณขึ้นมาใหม่
โปรดทราบว่าเมื่อเปรียบเทียบความเท่าเทียมกันของฟังก์ชันกับฟังก์ชันอื่น ฟังก์ชันดังกล่าวจะเป็นเท็จเสมอ เนื่องจากฟังก์ชันเป็นอ็อบเจ็กต์ด้วย มันจะเท่ากับตัวมันเองเท่านั้น:
// these variables contain the exact same function but they are not equal const hello = () => console.log('Hello Matt') const hello2 = () => console.log('Hello Matt') hello === hello2 // false hello === hello // true
กล่าวอีกนัยหนึ่ง เมื่อ React แสดงผลคอมโพเนนต์ของคุณอีกครั้ง จะเห็นฟังก์ชันใดๆ ที่ประกาศไว้ในคอมโพเนนต์ของคุณว่าเป็นฟังก์ชันใหม่
วิธีนี้ใช้ได้เกือบตลอดเวลา และฟังก์ชันทั่วไปก็คำนวณได้ง่ายและจะไม่ส่งผลต่อประสิทธิภาพการทำงาน แต่ในบางครั้งที่คุณไม่ต้องการให้ฟังก์ชันถูกมองว่าเป็นฟังก์ชันใหม่ คุณสามารถพึ่งพา useCallback
เพื่อช่วยคุณได้
คุณอาจกำลังคิดว่า "เมื่อใดที่ฉันไม่ต้องการให้ฟังก์ชันถูกมองว่าเป็นฟังก์ชันใหม่" มีบางกรณีที่ useCallback
เหมาะสมกว่า:

- คุณกำลังส่งฟังก์ชันไปยังองค์ประกอบอื่นที่บันทึกด้วย (
useMemo
) - ฟังก์ชันของคุณมีสถานะภายในที่ต้องจำไว้
- ฟังก์ชันของคุณขึ้นอยู่กับ hook อื่น เช่น
useEffect
เป็นต้น
ประโยชน์ด้านประสิทธิภาพของ React useCallback
เมื่อ useCallback
อย่างเหมาะสม จะช่วยเร่งความเร็วแอปพลิเคชันของคุณและป้องกันไม่ให้ส่วนประกอบแสดงผลซ้ำหากไม่ต้องการ
ตัวอย่างเช่น คุณมีองค์ประกอบที่ดึงข้อมูลจำนวนมากและมีหน้าที่แสดงข้อมูลนั้นในรูปแบบของแผนภูมิหรือกราฟ เช่นนี้
สมมติว่าองค์ประกอบหลักสำหรับการแสดงคอมโพเนนต์ของการแสดงข้อมูลเป็นภาพอีกครั้ง แต่อุปกรณ์ประกอบฉากหรือสถานะที่เปลี่ยนแปลงจะไม่ส่งผลต่อองค์ประกอบนั้น ในกรณีนั้น คุณอาจไม่ต้องการหรือจำเป็นต้องแสดงผลซ้ำและดึงข้อมูลทั้งหมด การหลีกเลี่ยงการเรนเดอร์ซ้ำและดึงข้อมูลนี้สามารถประหยัดแบนด์วิดท์ของผู้ใช้ของคุณและมอบประสบการณ์การใช้งานที่ราบรื่นยิ่งขึ้น
ข้อเสียของ React useCallback
แม้ว่าเบ็ดนี้สามารถช่วยคุณปรับปรุงประสิทธิภาพได้ แต่ก็ยังมีข้อผิดพลาดอยู่ด้วย สิ่งที่ต้องพิจารณาก่อนใช้ useCallback
(และ useMemo
) คือ:
- การ รวบรวมขยะ: ฟังก์ชันอื่นๆ ที่ยังไม่ได้บันทึกจะถูกทิ้งโดย React เพื่อเพิ่มหน่วยความจำ
- การ จัดสรรหน่วยความจำ: คล้ายกับการรวบรวมขยะ ยิ่งคุณมีฟังก์ชันที่จดจำได้มากเท่าใด หน่วยความจำก็จะยิ่งต้องการมากขึ้นเท่านั้น นอกจากนี้ ทุกครั้งที่คุณใช้การเรียกกลับเหล่านี้ จะมีโค้ดจำนวนมากใน React ที่ต้องใช้หน่วยความจำมากขึ้นเพื่อให้ได้รับแคชเอาต์พุต
- ความซับซ้อนของโค้ด: เมื่อคุณเริ่มห่อฟังก์ชันใน hooks เหล่านี้ คุณจะเพิ่มความซับซ้อนของโค้ดของคุณทันที ตอนนี้ต้องการความเข้าใจมากขึ้นว่าเหตุใดจึงใช้ตะขอเหล่านี้และยืนยันว่าใช้อย่างถูกต้อง
การตระหนักรู้ถึงข้อผิดพลาดข้างต้นสามารถช่วยให้คุณไม่ต้องปวดหัวจากการสะดุดล้มด้วยตัวเอง เมื่อพิจารณาใช้ useCallback
ตรวจสอบให้แน่ใจว่าผลประโยชน์ด้านประสิทธิภาพมีมากกว่าข้อเสีย
ตอบสนองการใช้งานตัวอย่างการโทรกลับ
ด้านล่างนี้คือการตั้งค่าอย่างง่ายด้วยส่วนประกอบปุ่มและส่วนประกอบตัวนับ ตัวนับมีสถานะสองส่วนและแสดงผลส่วนประกอบปุ่มสองส่วน โดยแต่ละส่วนจะอัปเดตส่วนแยกต่างหากของสถานะส่วนประกอบตัวนับ
ส่วนประกอบปุ่มมีอุปกรณ์ประกอบฉากสองอย่าง: handleClick
และชื่อ ทุกครั้งที่แสดงผลปุ่ม ปุ่มจะเข้าสู่คอนโซล
import { useCallback, useState } from 'react' const Button = ({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> } const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) return ( <> {countOne} {countTwo} <Button handleClick={() => setCountOne(countOne + 1)} name="button1" /> <Button handleClick={() => setCountTwo(countTwo + 1)} name="button1" /> </> ) }
ในตัวอย่างนี้ ทุกครั้งที่คุณคลิกที่ปุ่มใดปุ่มหนึ่ง คุณจะเห็นสิ่งนี้ในคอนโซล:
// counter rendered // button1 rendered // button2 rendered
ตอนนี้ถ้าเราใช้ useCallback
กับฟังก์ชัน handleClick
ของเราและห่อ Button ของเราใน React.memo
เราจะเห็นว่า useCallback
ให้อะไรกับเราบ้าง React.memo
นั้นคล้ายกับ useMemo
และช่วยให้เราสามารถบันทึกส่วนประกอบได้
import { useCallback, useState } from 'react' const Button = React.memo(({handleClick, name}) => { console.log(`${name} rendered`) return <button onClick={handleClick}>{name}</button> }) const Counter = () => { console.log('counter rendered') const [countOne, setCountOne] = useState(0) const [countTwo, setCountTwo] = useState(0) const memoizedSetCountOne = useCallback(() => setCountOne(countOne + 1), [countOne) const memoizedSetCountTwo = useCallback(() => setCountTwo(countTwo + 1), [countTwo]) return ( <> {countOne} {countTwo} <Button handleClick={memoizedSetCountOne} name="button1" /> <Button handleClick={memoizedSetCountTwo} name="button1" /> </> ) }
เมื่อเราคลิกปุ่มใดปุ่มหนึ่ง เราจะเห็นเฉพาะปุ่มที่เราคลิกเพื่อเข้าสู่ระบบคอนโซลเท่านั้น:
// counter rendered // button1 rendered // counter rendered // button2 rendered
เราใช้การท่องจำกับองค์ประกอบปุ่มของเรา และค่าพร็อพที่ส่งไปจะถือว่าเท่ากัน ฟังก์ชัน handleClick
ทั้งสองแบบถูกแคชไว้และ React จะถูกมองว่าเป็นฟังก์ชันเดียวกันจนกว่าค่าของรายการในอาร์เรย์การพึ่งพาจะเปลี่ยนไป (เช่น countOne
, countTwo
)
สรุป
เจ๋งพอๆ กับ useCallback
และ useMemo
โปรดจำไว้ว่าพวกเขามีกรณีการใช้งานเฉพาะ — คุณไม่ควรห่อทุกฟังก์ชันด้วย hooks เหล่านี้ หากฟังก์ชันมีความซับซ้อนในการคำนวณ การขึ้นต่อกันของ hook หรือ prop อื่นที่ส่งผ่านไปยังส่วนประกอบที่บันทึกเป็นตัวบ่งชี้ที่ดีที่คุณอาจต้องการเข้าถึง useCallback
เราหวังว่าบทความนี้จะช่วยให้คุณเข้าใจฟังก์ชัน React ขั้นสูง และช่วยให้คุณมีความมั่นใจมากขึ้นด้วยการเขียนโปรแกรมเชิงฟังก์ชันไปพร้อมกัน!