Skip to main content

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

· 3 min read

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...