React中Ref.current为null报错问题排查与修复求助
解决React组件中ref.current为null的点击事件报错问题
问题描述
- 初始阶段:Dropdown组件运行正常,可正常打开和关闭;
- 切换到Search组件并点击后,触发“wrapper.current is null”报错;
- 返回Dropdown页面并点击组件时,Dropdown和Search组件均出现大量报错;
- 刷新页面后,组件暂时恢复正常,但切换到其他组件后问题会再次出现。
报错原因分析
- 重复绑定且未清理全局事件:Search组件在
handleClick中每次点击都给document添加新的点击事件,且从未移除。组件卸载后这些事件依然存在,触发时会访问已销毁组件的ref,导致ref.current为null。 - 未处理组件卸载后的事件触发:Dropdown组件的
useEffect中添加了全局点击事件,但没有清理函数。组件卸载后事件触发时,dropdownWrapper.current或dropdown.current已不存在,直接调用contains会报错。 - 未先判断ref状态就调用方法:直接访问
ref.current.contains,未先检查ref.current是否存在,一旦ref未挂载或组件已卸载,就会抛出错误。
解决方案
- 所有全局事件绑定都要配套清理函数,组件卸载时移除事件监听,避免内存泄漏和无效引用;
- 调用
ref.current的方法前,先判断ref.current是否存在; - Search组件的全局点击事件改用
useEffect绑定,避免重复添加事件。
修改后的代码
Dropdown组件
import { useRef, useState, useEffect } from "react"; export default function Dropdown(props) { const [isActive, setIsActive] = useState(false); const dropdownWrapper = useRef(null); const dropdown = useRef(null); const handleClick = (event) => { setIsActive(true); }; useEffect(() => { const handleDocumentClick = (event) => { // 先判断ref是否存在,再调用contains if ( dropdownWrapper.current && dropdown.current && !dropdownWrapper.current.contains(event.target) && !dropdown.current.contains(event.target) ) { setIsActive(false); } }; document.addEventListener("click", handleDocumentClick); // 添加清理函数,组件卸载时移除事件监听 return () => { document.removeEventListener("click", handleDocumentClick); }; }, []); return ( <> <div ref={dropdownWrapper} onClick={handleClick} className="relative flex w-full cursor-pointer" > {props.children} {props.content} <div ref={dropdown} className={`${!isActive ? "hidden" : ""} absolute -left-0 top-full z-30 mb-[4px] flex w-full flex-col`} > <div className={`scroll flex max-h-[310px] min-w-max flex-col overflow-hidden overflow-y-auto rounded-b-[8px] bg-blue-50 py-[8px] shadow-mm-1 dark:bg-gray-800 ${props.apart && "mt-[4px] rounded-[8px]"}`} > {props.menu} </div> </div> </div> </> ); } export { Dropdown };
Search组件
import React, { useEffect, useRef, useState } from "react"; export default function Search(props) { const [isActive, setIsActive] = useState(false); const wrapper = useRef(null); const result = useRef(null); const handleClick = (event) => { setIsActive(true); }; useEffect(() => { const handleDocumentClick = (event) => { // 先判断ref是否存在,再调用contains if ( wrapper.current && result.current && !wrapper.current.contains(event.target) && !result.current.contains(event.target) ) { setIsActive(false); } }; document.addEventListener("click", handleDocumentClick); // 添加清理函数,组件卸载时移除事件监听 return () => { document.removeEventListener("click", handleDocumentClick); }; }, []); return ( <> <div ref={wrapper} onClick={handleClick} className={`relative flex w-full min-w-[360px] max-w-[720px] cursor-pointer ${isActive ? "rounded-t-[32px]" : "rounded-full"} bg-gray-800 `} > {props.children} <div ref={result} className={`${!isActive ? "hidden" : ""} absolute -left-0 top-full z-30 mb-[4px] flex w-full flex-col `} > <div className={`flex max-h-[310px] min-w-max flex-col overflow-hidden overflow-y-auto rounded-b-[32px] bg-blue-50 py-[8px] shadow-mm-1 dark:bg-gray-800`} > {props.result} </div> </div> </div> </> ); } export { Search };
内容的提问来源于stack exchange,提问作者sllmn
相关产品推荐
相关产品推荐

