Kodokon kodokon.com

คุณภาพ: Testing Library, StrictMode, Profiler

ทดสอบพฤติกรรมแทนที่จะเป็นการนำไปใช้งาน (implementation) ใช้ประโยชน์จากการเรียกซ้ำสองครั้งของ StrictMode และวัดการเรนเดอร์ด้วย Profiler

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

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

ปรัชญาของ Testing Library สรุปได้ในประโยคเดียว: ทดสอบสิ่งที่ผู้ใช้รับรู้ ไม่ใช่รายละเอียดการนำไปใช้งาน (implementation details) ในทางปฏิบัติ คุณสอบถาม DOM ผ่าน ต้นไม้การเข้าถึง (accessibility tree): getByRole ก่อน (ราชินีแห่งคิวรี ซึ่งตรวจสอบความถูกต้องของบทบาท ARIA ของคุณไปด้วยในตัว) จากนั้นคือ getByLabelText, getByText และ getByTestId เป็นทางเลือกสุดท้ายเท่านั้น สำหรับการโต้ตอบ ให้เลือก user-event มากกว่า fireEvent: ในขณะที่ fireEvent.change ปล่อยเหตุการณ์เดี่ยว ๆ ออกมา user.type จะจำลองลำดับเหตุการณ์ทั้งหมด (focus, keydown, keypress, input, keyup) เหมือนกับแป้นพิมพ์จริงเป๊ะ ตัวแปร findBy* รวมคิวรีเข้ากับการรอแบบอะซิงโครนัส: จำเป็นอย่างยิ่งหลังจากขั้นตอนการโหลด

JSX
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';

test('adds a task to the list', async () => {
  const user = userEvent.setup();
  render(<TodoApp />);

  await user.type(
    screen.getByRole('textbox'),
    'Review the report'
  );
  await user.click(
    screen.getByRole('button', { name: /add/i })
  );

  expect(
    await screen.findByText('Review the report')
  ).toBeInTheDocument();
});
การทดสอบที่เน้นพฤติกรรม ไม่ใช่การนำไปใช้งาน

StrictMode เป็นเครื่องมือสำหรับการพัฒนา (development) ที่ไม่มีผลใด ๆ เลยในโปรดักชัน ในระหว่างการพัฒนา มันเรียกฟังก์ชันคอมโพเนนต์ ตัวเริ่มต้นสเตต (state initializer) และฟังก์ชันที่ส่งให้ setState ของคุณ สองครั้ง เพื่อขับไล่การเรนเดอร์ที่ไม่บริสุทธิ์ออกมา: ถ้าการรันสองครั้งให้ผลลัพธ์ต่างกัน แสดงว่าคุณมีผลข้างเคียงที่ซ่อนอยู่ ตั้งแต่ React 18 มันยังรันแต่ละเอฟเฟกต์ผ่านวงจร mount, unmount, remount ด้วย: เอฟเฟกต์ที่การล้าง (cleanup) ไม่สมมาตรอย่างสมบูรณ์ (การสมัครสมาชิกที่ไม่เคยถูกยกเลิก คำร้องขอที่ไม่เคยถูก abort) จะเปิดโปงตัวเองทันที มันคือการเตรียมพร้อมโดยตรงสำหรับฟีเจอร์ที่ React ถอดแล้วติดตั้งหน้าจอใหม่โดยยังคงรักษาสเตตของมันไว้

JSX
useEffect(() => {
  const controller = new AbortController();
  fetch('/api/user', { signal: controller.signal })
    .then((res) => res.json())
    .then(setUser)
    .catch(() => {});
  return () => controller.abort();
}, []);
เอฟเฟกต์ที่สมมาตรจะรอดจากการติดตั้งซ้ำสองครั้งของ StrictMode

สำหรับการวัดผล React มีคอมโพเนนต์ <Profiler> และคู่หูแบบกราฟิกของมันใน DevTools ให้ใช้ คอลแบ็ก onRender ของมันรับ (นอกเหนือจากสิ่งอื่น ๆ) phase (mount, update, หรือ nested-update), actualDuration (เวลาที่ใช้ไปจริงในการเรนเดอร์ซับทรีสำหรับการคอมมิตครั้งนี้) และ baseDuration (การประมาณเวลาในการเรนเดอร์ซับทรีทั้งหมด โดยไม่มีการ memoize ใด ๆ) ช่องว่างระหว่างสองค่าวัดประสิทธิผลของ memo และ useMemo ของคุณ: actualDuration ที่ต่ำกว่า baseDuration มาก หมายความว่าการ memoize กำลังทำงานให้คุณ

JSX
import { Profiler } from 'react';

function App() {
  return (
    <Profiler
      id="Sidebar"
      onRender={(id, phase, actualDuration) => {
        console.log(id, phase, actualDuration);
      }}
    >
      <Sidebar />
    </Profiler>
  );
}
การวัดต้นทุนการเรนเดอร์จริงของซับทรี

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

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

  1. ทำไม StrictMode จึงติดตั้ง ถอด แล้วติดตั้งใหม่แต่ละเอฟเฟกต์ในระหว่างการพัฒนา?
    • เพื่อจำลองความหน่วงของเครือข่ายเซิร์ฟเวอร์
    • เพื่อตรวจสอบว่าการล้างของแต่ละเอฟเฟกต์นั้นสมมาตร
    • เพื่อล้างแคชภายในของ React
    • เพื่อบังคับการรวมกลุ่มการอัปเดต
  2. คิวรีของ Testing Library ตัวใดที่คุณควรใช้เป็นอันดับแรก?
    • getByTestId ตัวที่เสถียรที่สุด
    • getByClassName ตัวที่แม่นยำที่สุด
    • getByRole ที่สอดคล้องกับต้นไม้การเข้าถึง
    • querySelector ตัวที่ยืดหยุ่นที่สุด
  3. baseDuration แทนอะไรในคอลแบ็กของ Profiler?
    • เวลาของการคอมมิตครั้งล่าสุดเท่านั้น
    • เวลาเรนเดอร์โดยประมาณของซับทรีโดยไม่มีการ memoize ใด ๆ
    • เวลาที่ผ่านไปนับตั้งแต่คอมโพเนนต์ติดตั้ง
    • ระยะเวลาเฉลี่ยของการเรนเดอร์สิบครั้งล่าสุด