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

React实现点击输入框外部退出编辑且不触发其他可点击组件

解决编辑模式下点击外部仅退出编辑且不触发其他元素事件的问题

核心思路

通过捕获阶段的全局点击监听拦截输入框外的点击事件,阻止事件继续传播(避免触发其他元素的点击逻辑),同时执行退出编辑的操作;配合useRef判断点击区域是否属于输入框本身,确保输入框的正常交互不受影响。

修正后的完整代码

import React, { useState, useRef, useEffect } from "react";
import "./styles.css";

export default function App() {
  const itemList = [
    { done: false, title: "1st task" },
    { done: false, title: "2nd task" },
    { done: true, title: "3rd task" }
  ];

  const [todoItemList, setTodoItemList] = useState(itemList);

  const rename = (newName, index) => {
    // 修正状态更新:基于当前状态创建新数组,避免修改原对象
    setTodoItemList(prevList => 
      prevList.map((item, i) => {
        if (i === index) {
          return { ...item, title: newName };
        }
        return item;
      })
    );
  };

  return (
    <div className="App">
      {todoItemList.map((item, index) => (
        <TodoItem
          key={index}
          done={item.done}
          title={item.title}
          rename={(newName) => rename(newName, index)}
        />
      ))}
    </div>
  );
}

const TodoItem = ({ done, title, rename }) => {
  const [isEdit, setIsEdit] = useState(false);
  const inputRef = useRef(null);

  const submit = (value) => {
    // 重构submit,直接接收输入值,避免依赖事件对象
    rename(value);
    setIsEdit(false);
  };

  const handleOnKeyUp = (e) => {
    if (e.key === "Enter") {
      submit(e.target.value);
    }
  };

  useEffect(() => {
    if (!isEdit) return;

    const handleClickOutside = (e) => {
      // 判断点击目标是否在输入框内部
      if (inputRef.current && !inputRef.current.contains(e.target)) {
        // 阻止事件传播,避免触发其他元素的点击逻辑
        e.stopPropagation();
        e.preventDefault();
        // 提交当前输入值并退出编辑
        submit(inputRef.current.value);
      }
    };

    // 捕获阶段监听document点击,确保先于目标元素的事件执行
    document.addEventListener('click', handleClickOutside, true);
    return () => {
      document.removeEventListener('click', handleClickOutside, true);
    };
  }, [isEdit, submit]);

  return (
    <div
      style={{
        border: "1px black solid",
        margin: "10px auto",
        padding: "5px 0"
      }}
    >
      <input type="checkbox" defaultChecked={done} />
      {isEdit ? (
        <input
          ref={inputRef}
          autoFocus
          type="text"
          defaultValue={title}
          onBlur={() => submit(inputRef.current?.value || title)}
          onKeyUp={handleOnKeyUp}
        />
      ) : (
        <span onClick={() => setIsEdit(true)}>{title}</span>
      )}
    </div>
  );
};

关键细节说明

  1. 状态更新修正:原代码中rename函数直接修改初始列表itemList的对象,会导致状态异常。改为基于当前状态prevList创建新对象,符合React不可变状态的要求。
  2. 捕获阶段监听:使用addEventListener的第三个参数true开启捕获阶段监听,确保点击事件先被全局监听处理,再到达目标元素,从而阻止后续事件触发。
  3. 点击区域判断:通过inputRef.current.contains(e.target)判断点击是否在输入框内,避免误拦截输入框自身的点击操作。
  4. submit函数重构:不再依赖事件对象,直接接收输入值,提升代码灵活性。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.25 15:24:21