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
相关产品推荐
相关产品推荐

