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

React自定义Hook异常:多组件使用时回调同时触发求助

问题分析

你的useBase64 Hook存在两个核心问题导致异常:

  • 全局状态共享冲突:Hook依赖Redux全局的state.file.file状态,当任意组件触发文件选择并更新该状态后,所有使用该Hook的组件(Avatar和Cover)都会因file变化触发useEffect,进而执行各自的回调,导致两张图片同时渲染。
  • Ref传递错误:直接传递buttonRef.current给Hook,组件初始化时ref.current为null,且ref更新不会触发组件重渲染,会导致Hook无法及时绑定点击事件。
修复方案

重构useBase64 Hook,让每个实例独立管理文件输入,不再依赖全局状态:

修复后的useBase64 Hook

import { useEffect, useRef } from 'react';

export const useBase64 = (
  triggerRef: React.RefObject<HTMLButtonElement>,
  fn?: (base64Image: string) => void
) => {
  // 每个Hook实例独立维护文件输入元素
  const fileInputRef = useRef<HTMLInputElement | null>(null);

  useEffect(() => {
    if (!triggerRef.current) return;

    // 创建独立的隐藏文件输入
    if (!fileInputRef.current) {
      const input = document.createElement('input');
      input.type = 'file';
      input.accept = 'image/*';
      input.style.display = 'none';
      document.body.appendChild(input);
      fileInputRef.current = input;

      // 监听当前实例的文件选择事件
      input.addEventListener('change', (e) => {
        const file = (e.target as HTMLInputElement).files?.[0];
        if (!file || !fn) return;

        const reader = new FileReader();
        reader.readAsDataURL(file);
        reader.onload = () => {
          fn(reader.result as string);
          // 重置输入,允许重新选择同一张图片
          input.value = '';
        };
      });
    }

    const handleClick = () => {
      fileInputRef.current?.click();
    };

    triggerRef.current.addEventListener('click', handleClick);

    return () => {
      triggerRef.current?.removeEventListener('click', handleClick);
      // 组件卸载时清理创建的输入元素
      if (fileInputRef.current) {
        document.body.removeChild(fileInputRef.current);
        fileInputRef.current = null;
      }
    };
  }, [triggerRef, fn]);
};

修改组件调用方式

将传递buttonRef.current改为传递ref对象本身:

Avatar组件修改

// ...其他代码
const buttonRef = useRef<HTMLButtonElement>(null);
// 传递ref对象而非current
useBase64(buttonRef, (image) => {
  console.log("avatar triggered");
  dispatch(openImage({ image, type: "avatar" }));
});
// ...其他代码

Cover组件修改

// ...其他代码
const buttonRef = useRef<HTMLButtonElement>(null);
// 传递ref对象而非current
useBase64(buttonRef, (image) => {
  console.log("cover triggered");
  dispatch(openImage({ image, type: "cover" }));
});
// ...其他代码
修复说明
  1. 独立实例隔离:每个useBase64 Hook创建自己的文件输入元素,避免多个组件共用全局输入导致的冲突。
  2. 移除全局依赖:不再依赖Redux的全局file状态,只有当前点击组件对应的Hook会响应文件选择并触发回调。
  3. 正确Ref传递:传递ref对象确保useEffect能追踪ref变化,及时绑定/解绑事件。
  4. 自动清理资源:组件卸载时移除创建的文件输入元素,避免内存泄漏。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 05:55:36