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

React中子元素点击触发父元素事件监听器问题求助

问题解决:React技能选择组件点击候选项时addSkill不执行的问题

问题根因

点击候选列表选项时,父元素的onBlur事件会先于子元素的onClick触发——因为输入框失去焦点的动作比点击选项的动作更早完成。此时setIsFocus(false)会直接卸载候选列表组件,导致addSkill还没执行就没了。另外,e.stopPropagation()对blur事件无效,因为blur属于焦点事件,不参与冒泡传播,阻止事件传播根本起不到作用。

解决方案

这里提供两种可靠的解决方法:

方法一:在onBlur中判断点击目标是否为内部元素

修改父元素的onBlur处理逻辑,通过e.relatedTarget检查失去焦点后,焦点是否落在当前容器的内部元素上。只有点击组件外部时才关闭候选列表:

// 替换原父元素的onBlur函数
onBlur={(e) => {
  // 检查点击目标是不是当前容器的后代
  if (!e.currentTarget.contains(e.relatedTarget)) {
    setIsFocus(false);
  }
}}

方法二:用setTimeout延迟执行关闭逻辑

给onBlur里的setIsFocus加一个微小延迟,让onClick先执行完毕再关闭候选列表:

onBlur={() => {
  setTimeout(() => setIsFocus(false), 100);
}}

额外优化(可选):用mousedown代替click

把候选项的onClick换成onMouseDown,因为mousedown事件会在blur之前触发,确保addSkill先执行完成:

// 修改SkillsSuggestionList中的li事件
<li
  onMouseDown={(e) => {
    e.preventDefault(); // 可选,保留输入框焦点,方便连续添加技能
    addSkill(skill.name);
  }}
  key={skill.name}
>
  {skill.name}
</li>

修改后的完整代码示例

SolutionForm组件(方法一实现)

import { useRef, useState } from "react"

import fetchSkills from "../api"
import BaseInput from "../components/reusable/BaseInput"
import SkillsSuggestionList from "../components/solutions/SkillsSuggestionList"
import Icons from "../components/SvgIcons/Icons"
import { debounce } from "../utils/shared"

const SKILLS = [
  { name: "html" },
  { name: "css" },
  { name: "javascript" },
  { name: "bootstrap" },
  { name: "tailwind-css" },
  { name: "react" },
  { name: "vue" },
  { name: "angular" },
]

const SolutionForm = () => {
  const [skills, setSkills] = useState([])
  const [isFocus, setIsFocus] = useState(false)
  const [data, setData] = useState({ skills: [] }) // 补充原代码缺失的data状态
  const selectRef = useRef(null)

  const handleSelectChange = async (e) => {
    const { value } = e.target

    if (value.length > 2) {
      const result = await fetchSkills(value)
      setSkills(result.items)
    }
  }

  const addSkill = (skill) => {
      setData({
        ...data,
        skills: [...data.skills, skill],
      })
    setIsFocus(false)
    setSkills([])
    if (selectRef.current) selectRef.current.value = ""
  }

  const handleSubmit = (e) => {
    e.preventDefault()
    // 这里添加表单提交逻辑
  }

  return (
    <div>
      <div>
        <form onSubmit={handleSubmit}>
              <div
                key="skills-input-container"
                onBlur={(e) => {
                  if (!e.currentTarget.contains(e.relatedTarget)) {
                    setIsFocus(false)
                  }
                }}
              >
                <div>
                  <ul>
                    {data.skills.map((skill, index) => (
                      <li key={index}>
                          {skill}
                      </li>
                    ))}
                  </ul>
                  <BaseInput
                    onChange={debounce(handleSelectChange, 500)}
                    onFocus={() => setIsFocus(true)}
                    name="skills"
                    innerRef={selectRef}
                  />
                </div>
                {isFocus && (
                  <SkillsSuggestionList
                    skills={skills.length > 0 ? skills : SKILLS}
                    addSkill={addSkill}
                  />
                )}
              </div>
        </form>
      </div>
    </div>
  )
}

export default SolutionForm

SkillsSuggestionList组件(保持原代码即可)

const SkillsSuggestionList = ({ skills, addSkill }) => {
  return (
    <ul id="skills">
      {skills.map((skill) => (
        <li
          onClick={() => addSkill(skill.name)}
          key={skill.name}
        >
          {skill.name}
        </li>
      ))}
    </ul>
  )
}

export default SkillsSuggestionList

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.07 11:20:33