ทดสอบพฤติกรรมแทนที่จะเป็นการนำไปใช้งาน (implementation) ใช้ประโยชน์จากการเรียกซ้ำสองครั้งของ StrictMode และวัดการเรนเดอร์ด้วย Profiler
เปิดบทเรียนนี้ใน 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* รวมคิวรีเข้ากับการรอแบบอะซิงโครนัส: จำเป็นอย่างยิ่งหลังจากขั้นตอนการโหลด
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 ถอดแล้วติดตั้งหน้าจอใหม่โดยยังคงรักษาสเตตของมันไว้
useEffect(() => {
const controller = new AbortController();
fetch('/api/user', { signal: controller.signal })
.then((res) => res.json())
.then(setUser)
.catch(() => {});
return () => controller.abort();
}, []);สำหรับการวัดผล React มีคอมโพเนนต์ <Profiler> และคู่หูแบบกราฟิกของมันใน DevTools ให้ใช้ คอลแบ็ก onRender ของมันรับ (นอกเหนือจากสิ่งอื่น ๆ) phase (mount, update, หรือ nested-update), actualDuration (เวลาที่ใช้ไปจริงในการเรนเดอร์ซับทรีสำหรับการคอมมิตครั้งนี้) และ baseDuration (การประมาณเวลาในการเรนเดอร์ซับทรีทั้งหมด โดยไม่มีการ memoize ใด ๆ) ช่องว่างระหว่างสองค่าวัดประสิทธิผลของ memo และ useMemo ของคุณ: actualDuration ที่ต่ำกว่า baseDuration มาก หมายความว่าการ memoize กำลังทำงานให้คุณ
import { Profiler } from 'react';
function App() {
return (
<Profiler
id="Sidebar"
onRender={(id, phase, actualDuration) => {
console.log(id, phase, actualDuration);
}}
>
<Sidebar />
</Profiler>
);
}baseDuration แทนอะไรในคอลแบ็กของ Profiler?