Skip to main content

การใช้งานและความแตกต่างระหว่าง useMemo และ useCallback ของ React Hooks

Kongvut Sangkla

Intro

สวัสดีครับ บทความนี้จะแนะนำแนวทางการใช้งาน React Hooks ที่ชื่อ useMemo และ useCallback คืออะไร มีความสำคัญอย่างไร ช่วยเพิ่มประสิทธิภาพลดการ Render ของ React ที่หนักหน่วงอย่างไร ควร Refactor อย่างไร มาดูกันเลยครับ 😍

TL; DR;

  • useMemo คือการ Cache ค่า Value ถูกเรียกครั้งแรกเมื่อมีการ Render และครั้งต่อไปเมื่อมีการ Re-Render และ ค่าใน Array deps มีการเปลี่ยนแปลง โดย Return ออกเป็นค่า Value
  • useCallback คือการ Cache Function ไม่ ถูกเรียกครั้งแรกเมื่อมีการ Render โดยจะถูกเรียกก็ต่อเมื่อสั่ง Call ฟังก์ชัน และ ค่าใน Array deps มีการเปลี่ยนแปลง โดย Return ออกเป็น Function

Imgur

เริ่มกันเลย

ปัญหา Re-Render

ปัญหาการ Re-Render นั้นเป็นปัญหาใหญ่ ที่ทำให้เกิดการ Render ที่หนักหน่วง ตามปกติทุกการ Render จะเกิดการจอง Memory (Memory allocation) และบางทีการ Render Component ซ้ำ ๆ เกินความจำเป็นอาจจะทำให้เกิดการจัดสรรหน่วยความจำอย่างไม่ถูกต้อง (Memory Leak) ได้

แก้ปัญหาด้วย useMemo

ในตัวอย่างนี้จะใช้การวิธีการแสดงค่า Timestamp ของฟังก์ชัน Date เป็นกรณียกตัวอย่างเมื่อหน้าเว็บถูก Re-Render ในแต่ละครั้ง

Live Editor
() => {
  const [number, setNumber] = useState(0)
  const [someValue, setSomeValue] = useState(0)

  const getTimestamp = () => (new Date().getTime())
  
  const numberWithoutMemo = getTimestamp()

  const getNumberWithMemo = useMemo(() => {
    return getTimestamp()
  }, [someValue])

  return (
    <>
      <p>แบบไม่ใช้ useMemo: {numberWithoutMemo}</p>
      <hr />
      <p>แบบใช้ useMemo: {getNumberWithMemo}</p>
      <hr />
      ทดลองเปลี่ยนค่า (Number): {number}<br/>
      <button onClick={() => { setNumber((prev) => ++prev) }}>เพิ่ม (+)</button>
      <button onClick={() => { setNumber((prev) => --prev) }}>ลด (-)</button>
      <hr />
      ทดลองเปลี่ยนค่า (Some value): {someValue}<br />
      <button onClick={() => { setSomeValue((prev) => ++prev) }}>เพิ่ม (+)</button>
      <button onClick={() => { setSomeValue((prev) => --prev) }}>ลด (-)</button>
    </>
  )
}
Result
Loading...

แบบไม่ใช้ useMemo

  • Timestamp แสดงครั้งแรกเมื่อมีการ Render
  • Timestamp แสดงอีกครั้งเมื่อมีการ Re-Render (Number หรือ Some Value เปลี่ยนแปลง)
  • เป็นตัวอย่างกรณีการ Re-Render เกินความจำเป็น

แบบใช้ useMemo

  • Timestamp แสดงครั้งแรกเมื่อมีการ Render
  • Timestamp แสดงอีกครั้งเมื่อมีการ Re-Render และ ค่า Some Value มีการเปลี่ยนแปลงเท่านั้น
  • เป็นตัวอย่างกรณีการ Re-Render เฉพาะที่จำเป็น

แก้ปัญหาด้วย useCallback

ในตัวอย่างนี้จะใช้การวิธีการแสดงค่า Timestamp ของฟังก์ชัน Date เป็นกรณียกตัวอย่างเมื่อหน้าเว็บถูก Re-Render ในแต่ละครั้ง

Live Editor
() => {
  const [number, setNumber] = useState(0)
  const [someValue, setSomeValue] = useState(0)
  const [numberWithCallback, setNumberWithCallback] = useState(undefined)

  const getTimestamp = () => (new Date().getTime())
  
  const numberWithoutCallback = getTimestamp()

  const getNumber = useCallback(() => {
    setNumberWithCallback(() => getTimestamp())
  }, [someValue])
  
  return (
    <>
      <p>แบบไม่ใช้ useCallback: {numberWithoutCallback}</p>
      <hr />
      <p>แบบใช้ useCallback: {numberWithCallback}</p>
      <hr />
      ทดลองเปลี่ยนค่า (Number): {number}<br />
      <button onClick={() => { setNumber((prev) => ++prev) }}>เพิ่ม (+)</button>
      <button onClick={() => { setNumber((prev) => --prev) }}>ลด (-)</button>
      <hr />
      ทดลองเปลี่ยนค่า (Some value): {someValue}<br />
      <button onClick={() => { 
        setSomeValue((prev) => ++prev)
        getNumber()
      }}>เพิ่ม (+)</button>
      <button onClick={() => { 
        setSomeValue((prev) => --prev)
        getNumber()
      }}>ลด (-)</button>
    </>
  )
}
Result
Loading...

แบบไม่ใช้ useCallback

  • Timestamp แสดงครั้งแรกเมื่อมีการ Render
  • Timestamp แสดงอีกครั้งเมื่อมีการ Re-Render (Number หรือ Some Value เปลี่ยนแปลง)
  • เป็นตัวอย่างกรณีการ Re-Render เกินความจำเป็น

แบบใช้ useCallback

  • Timestamp ไม่แสดงครั้งแรกเรียกเมื่อมีการ Render (จะแสดงเมื่อเราสั่ง Call ฟังก์ชันเอง)
  • Timestamp แสดงเมื่อสั่ง Call ฟังก์ชัน และ ค่าใน Some Value มีการเปลี่ยนแปลงเท่านั้น
  • เป็นตัวอย่างกรณีการ Re-Render เฉพาะที่จำเป็น

สรุป

หลักการของ useMemo คือการ Cache ข้อมูลไว้

  • ถูกเรียกครั้งแรกเมื่อมีการ Render
  • ถูกเรียกอีกครั้งเมื่อมีการ Re-Render และ ค่าใน Array deps มีการเปลี่ยนแปลง
  • Return ออกเป็นค่า Value

หลักการของ useCallback คือการ Cache ฟังก์ชันไว้

  • ไม่ ถูกเรียกเมื่อมีการ Render (จะถูกเรียกเมื่อเราสั่ง Call ฟังก์ชันเอง)
  • ถูกเรียกอีกครั้งเมื่อสั่ง Call ฟังก์ชัน และ ค่าใน Array deps มีการเปลี่ยนแปลง
  • Return ออกเป็น Function
note

useCallback(fn, deps) จะเท่ากับ useMemo(() => fn, deps)

ทั้งสองตัวมีการทำงานที่คล้ายกัน และแตกต่างกันนิดหน่อย แต่ทั้งสองสามารถช่วยเพิ่มประสิทธิภาพเพื่อช่วยลดการ Re-Render ในบางกรณีที่เกินความจำเป็นได้ และต้องเลือกใช้ตามความเหมาะสมดังนี้

  • เมื่อ Client ใช้ Device สเปคเครื่องไม่ค่อยแรง React อาจจะหน่วงและช้ามาก ๆ ซึ่งการ Optimize render ก็อาจจะช่วยไปได้เยอะมาก ๆ
  • useMemo ใช้เมื่อถ้ามีการคำนวณอะไรที่ใช้เวลานานมาก ๆ และต้องการทำ Memorizes ไว้ แต่ในการกลับกัน ไม่ควรใช้กับการคำนวณง่าย ๆ เช่น 1 + 1 เพราะเป็นปกติจะได้ผลลัพธ์เร็วอยู่แล้ว และอาจจะทำให้เกิด Overhead เปล่า ๆ
  • เมื่อมี Component ซับซ้อนและเยอะ และมี Logic เยอะ ที่เป็น Parent Child หลายชั้น ถ้าไม่มีการทำ Optimize render อาจจะช้าและค้างได้เลย
  • Input Form ที่มีฟิลด์เยอะ ๆ ถ้าไม่คำนึงเรื่องพวกนี้ ถ้าเวลาพิมพ์ เร็ว ๆ จะสะดุดมาก ๆ

รายละเอียดเทคนิคอื่น ๆ

References

Loading...