You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何用Jest和React Testing Library测试记忆化组件不重渲染?

测试React记忆化组件是否避免重渲染的方案

背景:提交代码后发现React应用性能下降,排查后确认是引入的Context干扰了组件的Memoization效果。现在需要编写测试,验证用户操作后,被memo包裹的子组件不会发生重渲染。

现有代码结构

App.jsx

import { MemoizedChildComponent } from "./ChildComponent"

export function App() {
 return (
  <div>
   {/* 其他业务代码 */}
   <MemoizedChildComponent prop1={...} prop2={...}/>
   {/* 其他业务代码 */}
  </div>
 )
}

ChildComponent.jsx

import { memo } from "react"

function ChildComponent({ prop1, prop2,... }) {
 return (
   {/* 组件内容 */}
 )
}

const MemoizedChildComponent = memo(ChildComponent);

export { MemoizedChildComponent };

测试框架代码

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { App } from "./App"

test("ChildComponent was not rerendered", async () => {
  render(<App />);
  await userEvent.click(/* 触发状态变化的元素 */)

  // 需在此断言ChildComponent未重渲染
})

疑问:查阅React Testing Library API未找到监听渲染事件的方法;考虑过Jest mock,但不想替换原组件,只想监听渲染次数,有没有可行方案?


方案1:通过Jest Mock临时添加渲染计数(不修改生产代码)

该方案通过mock子组件模块,在保留原组件逻辑和memo特性的前提下,添加渲染次数统计,完全不影响生产代码。

测试代码实现

import React from 'react';
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { App } from "./App";

// Mock ChildComponent模块,注入渲染计数逻辑
jest.mock('./ChildComponent', () => {
  const originalModule = jest.requireActual('./ChildComponent');
  const renderCount = { current: 0 };

  // 包装原组件函数,每次调用时递增计数
  const SpyWrappedChild = (...args) => {
    renderCount.current += 1;
    return originalModule.ChildComponent(...args);
  };

  // 用memo重新包装带计数的组件,保留原记忆化逻辑
  const MemoizedChildComponent = React.memo(SpyWrappedChild);

  // 导出原模块内容,替换目标组件并新增计数变量
  return {
    ...originalModule,
    MemoizedChildComponent,
    renderCount,
  };
});

// 导入mock后的计数变量
import { renderCount } from './ChildComponent';

test("ChildComponent was not rerendered", async () => {
  render(<App />);
  // 初始渲染后,子组件应渲染1次
  expect(renderCount.current).toBe(1);

  // 触发用户操作(比如点击会改变父组件/Context状态的元素)
  await userEvent.click(screen.getByText('触发操作的按钮'));

  // 断言渲染次数未增加,说明memo生效,未重渲染
  expect(renderCount.current).toBe(1);
});

方案2:给组件添加测试专用渲染计数(需修改少量代码)

如果不想使用mock,可以在子组件中添加仅在测试环境生效的计数逻辑,通过ref传递统计次数。

1. 修改ChildComponent.jsx

import { memo, useEffect } from "react"

function ChildComponent({ prop1, prop2, testRenderCountRef }) {
  // 仅在测试环境统计渲染次数
  useEffect(() => {
    if (testRenderCountRef) {
      testRenderCountRef.current += 1;
    }
  });

  return (
    {/* 原组件内容 */}
  )
}

const MemoizedChildComponent = memo(ChildComponent);

export { MemoizedChildComponent };

2. 修改App.jsx

import { MemoizedChildComponent } from "./ChildComponent"

export function App({ testRenderCountRef }) {
 return (
  <div>
   {/* 其他业务代码 */}
   <MemoizedChildComponent 
     prop1={...} 
     prop2={...}
     testRenderCountRef={testRenderCountRef}
   />
   {/* 其他业务代码 */}
  </div>
 )
}

3. 编写测试

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { App } from "./App"

test("ChildComponent was not rerendered", async () => {
  const renderCountRef = { current: 0 };
  render(<App testRenderCountRef={renderCountRef} />);

  expect(renderCountRef.current).toBe(1);

  await userEvent.click(screen.getByText('触发操作的按钮'));

  expect(renderCountRef.current).toBe(1);
});

内容的提问来源于stack exchange,提问作者H-H

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.30 20:10:03