React自定义Hook返回null且useEffect未触发问题排查
问题概述
我实现了一个用于转换SVG视口坐标的自定义Hook useMouseSVGLocation,但编写单元测试时发现Hook始终返回初始的null值,useEffect似乎未执行,导致测试断言失败。
自定义Hook代码
import React, { useState, useEffect } from "react"; export default function useMouseSVGLocation( { clientX, clientY }, [left, top], [canvas_height] ) { const [svgX, setSvgX] = useState(null); const [svgY, setSvgY] = useState(null); useEffect(() => { setSvgX(clientX - left); setSvgY(canvas_height - (clientY - top)); console.log(`${clientX - left}`); }); return [svgX, svgY]; }
单元测试代码
import useMouseSVGLocation from "../useMouseSVGLocation"; import React from "react"; import Enzyme, { shallow } from "enzyme"; import Adapter from "enzyme-adapter-react-16"; import toJson from "enzyme-to-json"; Enzyme.configure({ adapter: new Adapter() }); function TestComponent() { const [svgX, svgY] = useMouseSVGLocation( { clientX: 100, clientY: 50 }, [100, 200], [200] ); return ( <div> {svgX}, {svgY} </div> ); } describe("test useMouseSVGLocation custom hook", () => { it("test it in a function component", () => { let wrapper = shallow(<TestComponent></TestComponent>); wrapper.update(); expect(toJson(wrapper)).toMatchSnapshot(); expect(wrapper.find("div").text()).toEqual("0, 350"); }); });
错误测试结果
● test useMouseSVGLocation custom hook › test it in a function component
expect(received).toEqual(expected) // deep equality
Expected: "0, 350"
Received: ", "
快照内容:
// Jest Snapshot v1, ...
exports[
test useMouseSVGLocation custom hook test it in a function component 1] =<div> , </div>;
问题原因及解决方案
原因1:useEffect缺少依赖数组
原Hook中的useEffect未指定依赖数组,这会导致它在每次组件渲染时都执行,但在测试环境中,这种无依赖的副作用可能无法被正确触发或捕获,同时也违反React Hook规范,会触发ESLint依赖缺失警告。
原因2:Shallow渲染对Hook副作用支持有限
Enzyme的shallow渲染仅渲染组件本身,不会完整模拟React的生命周期副作用(比如useEffect的执行),因此无法捕获状态更新后的组件渲染结果。
修复方案
给
useEffect添加正确的依赖数组
修改Hook中的useEffect,将所有用到的外部变量加入依赖数组,确保副作用仅在依赖变化时执行:useEffect(() => { setSvgX(clientX - left); setSvgY(canvas_height - (clientY - top)); console.log(`${clientX - left}`); }, [clientX, clientY, left, top, canvas_height]); // 添加依赖数组改用
mount渲染替代shallow
在测试中使用mount完整模拟组件生命周期,确保useEffect被执行:// 导入mount import Enzyme, { mount } from "enzyme"; // 修改测试代码中的渲染方式 let wrapper = mount(<TestComponent></TestComponent>);用
act包裹渲染确保副作用完成
用react-dom/test-utils中的act包裹组件渲染,确保所有副作用执行完成后再进行断言:import { act } from "react-dom/test-utils"; // 修改测试用例 it("test it in a function component", () => { let wrapper; act(() => { wrapper = mount(<TestComponent></TestComponent>); }); wrapper.update(); expect(toJson(wrapper)).toMatchSnapshot(); expect(wrapper.find("div").text()).toEqual("0, 350"); });
内容的提问来源于stack exchange,提问作者cdhit

