Kodokon kodokon.com

Memoization: memo, useMemo, useCallback

เรียนรู้ว่าเมื่อไหร่ React.memo, useMemo และ useCallback จะเร่งความเร็วแอปของคุณได้จริง และเมื่อไหร่ที่มันทำให้ซับซ้อนขึ้นโดยเปล่าประโยชน์

10 นาที · 3 คำถาม

เปิดบทเรียนนี้ใน Kodokon

เมื่อ component หนึ่ง re-render ลูกทั้งหมดของมันจะ re-render โดยปริยาย ไม่ว่า props ของพวกมันจะเปลี่ยนหรือไม่ นั่นไม่ใช่โศกนาฏกรรม: การ render จะแตะ DOM ก็ต่อเมื่อผลลัพธ์เปลี่ยนเท่านั้น Memoization จะมีความหมายเมื่อการ render บ่อยๆ มาพบกับ subtree ที่มีค่าใช้จ่ายสูง สามเครื่องมือ สามเป้าหมาย: React.memo ข้ามการ render ของ component หาก props ของมันเท่ากันแบบตื้น, useMemo แคช ค่า ที่คำนวณได้, useCallback แคช ฟังก์ชัน ทั้งสามอาศัยความเท่ากันของ reference

JSX
import { memo } from "react";

const Row = memo(function Row({ item, onSelect }) {
  return (
    <li onClick={() => onSelect(item.id)}>{item.label}</li>
  );
});
Row จะ re-render เฉพาะเมื่อ item หรือ onSelect เปลี่ยน reference

โซ่จะแข็งแรงเท่ากับข้อที่อ่อนที่สุดของมัน หาก prop เพียงตัวเดียวได้รับ reference ใหม่ในทุก render - arrow function แบบ inline, object หรือ array literal - การเปรียบเทียบของ memo จะล้มเหลวและ component ก็ re-render อยู่ดี useCallback ทำให้ reference ของฟังก์ชันคงที่เพื่อให้การเปรียบเทียบสำเร็จ

JSX
import { useCallback } from "react";

function List({ items }) {
  const handleSelect = useCallback((id) => {
    console.log("selected:", id);
  }, []);

  return (
    <ul>
      {items.map((item) => (
        <Row
          key={item.id}
          item={item}
          onSelect={handleSelect}
        />
      ))}
    </ul>
  );
}
handleSelect คง reference เดิมในทุก render

useMemo มีการใช้งานที่ชอบธรรมอยู่สองแบบ: หลีกเลี่ยง การคำนวณที่มีค่าใช้จ่ายสูงจริงๆ (การเรียงลำดับหรือรวมข้อมูลหลายพันรายการ) และ ทำให้ reference คงที่ ที่ถูกใช้โดยลูกที่ถูก memoize หรือถูกระบุใน dependency ของ effect สำหรับการคำนวณเล็กน้อย แคชมีค่าใช้จ่ายมากกว่าที่มันประหยัดได้: การเปรียบเทียบ dependency, หน่วยความจำที่ถูกกันไว้, การอ่านที่หนักขึ้น

JSX
import { useMemo } from "react";

function Stats({ orders }) {
  const total = useMemo(() => {
    return orders.reduce(
      (sum, order) => sum + order.amount,
      0
    );
  }, [orders]);

  return <p>Total: {total} €</p>;
}
total ถูกคำนวณใหม่เฉพาะเมื่อ orders เปลี่ยน reference

ทดสอบความรู้

ตรวจสอบว่าคุณจำประเด็นสำคัญของบทเรียนนี้ได้ครบถ้วน

  1. ลูกถูกห่อด้วย React.memo แต่ parent ของมันส่ง arrow function แบบ inline เป็น prop ให้ ผลลัพธ์คืออะไร?
    • Memoization ทำงาน: ฟังก์ชันถูกเปรียบเทียบด้วย source code ของมัน
    • Memoization ไม่ได้ผล: ฟังก์ชัน inline สร้าง reference ใหม่ในทุก render และการเปรียบเทียบล้มเหลว
    • React แสดง warning ในโหมด development
  2. useCallback(fn, [a]) รับประกันอะไรตราบใดที่ a ไม่เปลี่ยน?
    • ว่าฟังก์ชันจะไม่ถูกสร้างใหม่ในหน่วยความจำเลย
    • ว่าฟังก์ชันจะถูกเรียกใช้เพียงครั้งเดียว
    • ว่า reference ของฟังก์ชันเดิมจะถูกคืนจาก render หนึ่งไปยังอีก render หนึ่ง
  3. ในกรณีใดที่ useMemo มีเหตุผลชัดเจน?
    • การคำนวณที่มีค่าใช้จ่ายสูง หรือ reference ที่ต้องทำให้คงที่สำหรับลูกที่ถูก memoize หรือ dependency ของ effect
    • ค่าที่ได้มาใดๆ ก็ตาม เพื่อความปลอดภัยไว้ก่อน
    • การแคชข้อมูลข้ามการ mount สองครั้งของ component