测试行为而非实现,善用 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 化的情况下渲染整棵子树所需时间的一个估算)。二者之间的差距衡量了你的 memo 和 useMemo 的有效性:actualDuration 远低于 baseDuration 意味着 memo 化正在为你发挥作用。
import { Profiler } from 'react';
function App() {
return (
<Profiler
id="Sidebar"
onRender={(id, phase, actualDuration) => {
console.log(id, phase, actualDuration);
}}
>
<Sidebar />
</Profiler>
);
}baseDuration 代表什么?