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

React中如何为div设置焦点以触发onBlur实现确认框外部点击关闭?

问题分析与解决方案

焦点机制的误解

你遇到的核心问题是React状态更新的异步性:调用setShowApproval后,approval-box的可见状态不会立即改变,此时直接调用focus()会因为元素还处于不可见/不可交互状态而失败。另外,多个确认框共用tabIndex='1'会导致焦点冲突,且无法保证同一时间仅一个确认框激活。

修复步骤与优化方案

1. 解决焦点设置失败的问题

利用useEffect监听showApproval状态变化,确认元素可见后再设置焦点:

import { useRef, useEffect } from 'react';
import { useAppContext } from '../../App';
import { BsX } from 'react-icons/bs';

const useFocus = () => {
  const htmlElRef = useRef(null);
  const setFocus = () => {
    htmlElRef.current && htmlElRef.current.focus();
  };

  return [htmlElRef, setFocus];
};

const DeleteButton = ({ note, showApproval, setShowApproval }) => {
  const { deleteSelectedNote } = useAppContext();
  const [approvalBoxRef, setApprovalBoxFocus] = useFocus();

  // 状态变为true时,确保元素可见后再设置焦点
  useEffect(() => {
    if (showApproval) {
      setApprovalBoxFocus();
    }
  }, [showApproval, setApprovalBoxFocus]);

  const handleClickDelete = () => {
    setShowApproval(!showApproval);
  };

  const handleConfirmDelete = (noteId) => {
    deleteSelectedNote(noteId);
    setShowApproval(false);
  };

  return (
    <div className='delete-btn-container'>
      <button
        className={showApproval ? 'delete-btn btn pending' : 'delete-btn btn'}
        onClick={handleClickDelete}
      >
        <BsX size={'1em'} />
      </button>

      <div
        className={showApproval ? 'approval-box active' : 'approval-box'}
        tabIndex='-1' {/* 用-1允许JS设置焦点,不干扰Tab导航 */}
        onBlur={() => {
          setShowApproval(false);
          console.log('approval removed');
        }}
        ref={approvalBoxRef}
      >
        Delete?
        <span className='yes' onClick={() => handleConfirmDelete(note.id)}>
          {' '}Y{' '}
        </span>
        /
        <span className='no' onClick={() => setShowApproval(false)}>
          {' '}N
        </span>
      </div>
    </div>
  );
};
export default DeleteButton;

2. 确保同一时间仅一个确认框激活

将确认框的激活状态提升到父组件,用单个变量存储当前激活的笔记ID,替代每个按钮独立的布尔状态:

// 父组件(笔记列表)示例
import { useState } from 'react';
import DeleteButton from './DeleteButton';

const NoteList = ({ notes }) => {
  const [activeApprovalId, setActiveApprovalId] = useState(null);

  return (
    <div className='note-list'>
      {notes.map(note => (
        <div key={note.id} className='note-item'>
          {/* 笔记内容 */}
          <DeleteButton
            note={note}
            showApproval={activeApprovalId === note.id}
            setShowApproval={() => {
              setActiveApprovalId(prev => prev === note.id ? null : note.id);
            }}
          />
        </div>
      ))}
    </div>
  );
};

此方案天然保证同一时间仅一个确认框可见,避免多个弹窗同时出现的混乱。

3. 更稳定的替代方案:点击外部区域关闭

如果不想依赖焦点机制,可通过监听全局点击事件实现,兼容性更好:

// 在DeleteButton组件中添加以下代码
useEffect(() => {
  const handleClickOutside = (e) => {
    if (showApproval && approvalBoxRef.current && !approvalBoxRef.current.contains(e.target)) {
      setShowApproval(false);
    }
  };

  document.addEventListener('mousedown', handleClickOutside);
  return () => document.removeEventListener('mousedown', handleClickOutside);
}, [showApproval]);

该方案无需设置焦点,直接判断点击事件是否发生在确认框外部,逻辑更直观。

关键注意点

  • React状态更新是异步的,不要在设置状态后立即操作DOM,用useEffect监听状态变化再执行
  • 使用tabIndex='-1'代替1,避免干扰正常的Tab导航顺序
  • 统一管理多个确认框的激活状态,避免组件间状态冲突

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.22 05:17:20