اختبر السلوك بدلاً من التطبيق، واستثمر الاستدعاءات المزدوجة لـ StrictMode، وقِس عمليات العرض بواسطة Profiler.
افتح هذا الدرس في Kodokonفلسفة Testing Library تتلخّص في جملة واحدة: اختبر ما يدركه المستخدم، لا تفاصيل التطبيق أبداً. عملياً، تستعلم الـ DOM عبر شجرة إمكانية الوصول: 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 أداة تطوير من دون أيّ أثر إطلاقاً في الإنتاج. في التطوير، يستدعي دوال مكوّناتك، ومُهيّئات الحالة، والدوال المُمرَّرة إلى setState مرّتين، لاستخراج عمليات العرض غير النقيّة: إذا أعطى التنفيذان نتيجتين مختلفتين، فلديك أثر جانبي خفيّ. ومنذ React 18، يُشغّل أيضاً كل تأثير عبر دورة التركيب، الفكّ، إعادة التركيب: التأثير الذي لا يكون تنظيفه متناظراً تماماً (اشتراك لم يُلغَ أبداً، طلب لم يُجهَض أبداً) يفضح نفسه فوراً. إنه تحضير مباشر لميزات يفكّ فيها 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 (تقدير للوقت اللازم لعرض الشجرة الفرعية بأكملها من دون أيّ حفظ في الذاكرة). الفجوة بينهما تقيس فعالية memo وuseMemo لديك: actualDuration أدنى بكثير من baseDuration يعني أن الحفظ في الذاكرة يعمل لصالحك.
import { Profiler } from 'react';
function App() {
return (
<Profiler
id="Sidebar"
onRender={(id, phase, actualDuration) => {
console.log(id, phase, actualDuration);
}}
>
<Sidebar />
</Profiler>
);
}baseDuration في نداء Profiler المرتجَع؟