Kodokon kodokon.com

质量:Testing Library、StrictMode、Profiler

测试行为而非实现,善用 StrictMode 的双重调用,并用 Profiler 测量渲染。

10 分钟 · 3 题

在 Kodokon 中打开本课

Testing Library 的哲学可以用一句话概括:测试用户所感知的东西,永远不要测试实现细节。具体来说,你通过无障碍树来查询 DOM:优先用 getByRole(查询之王,它顺带验证你的 ARIA 角色),然后是 getByLabelTextgetByText,只有在万不得已时才用 getByTestId。对于交互,优先选择 user-event 而非 fireEventfireEvent.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 是一个开发工具,在生产环境中毫无影响。在开发环境中,它会两次调用你的组件函数、状态初始化器,以及传给 setState 的函数,以揪出不纯的渲染:如果两次执行给出不同的结果,你就有一个隐藏的副作用。自 React 18 起,它还会让每个副作用经历挂载、卸载、重新挂载的循环:一个清理不完全对称的副作用(订阅从未取消、请求从未中止)会立刻暴露自己。这是对那些让 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 回调接收(除其他信息外)phasemountupdatenested-update)、actualDuration(本次提交中实际花在渲染子树上的时间),以及 baseDuration(对在完全没有任何 memo 化的情况下渲染整棵子树所需时间的一个估算)。二者之间的差距衡量了你的 memouseMemo 的有效性:actualDuration 远低于 baseDuration 意味着 memo 化正在为你发挥作用。

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. 在 Profiler 回调中,baseDuration 代表什么?
    • 仅最后一次提交的时间
    • 在没有任何 memo 化的情况下子树的估算渲染时间
    • 自组件挂载以来经过的时间
    • 最近十次渲染的平均时长