React父组件如何识别被点击的子组件?
可复用React选择组件实现问题
需求背景
我要开发一个可复用React组件,支持在多页面使用,接收多个同类型子组件作为选项。类似react-select的基础用法,但要把「选项」以子元素形式传入,而非通过props的options列表,选中状态和点击回调由父组件管理。
当前实现
当前写法需要在每个子组件上手动绑定selected和onClick:
enum Children { first = "first", second = "second", // ... } const [child, setChild] = useState(Children.first) <ChildrenSelect> <Child selected={child === Children.first} onClick={() => setChild(Children.first)} /> <Child selected={child === Children.second} onClick={() => setChild(Children.second)} // 更多props /> {/* 可能有更多子组件 */} </ChildrenSelect>
期望简化写法
希望把selected和点击逻辑移到ChildrenSelect组件上,子组件只需要传入标识ID:
const childClickHandler = (c: Children) => setChild(c); <ChildrenSelect selected={child} // 类型为Children onChildClick={childClickHandler} // 类型为(c: Children) => void > <Child id={Children.first} /> <Child id={Children.second} /> {/* 可能有更多子组件 */} </ChildrenSelect>
但遇到问题:ChildrenSelect组件如何识别被点击的子组件?
进展与新问题
我通过遍历子组件并修改props的方式实现了初步方案,ChildrenSelect组件代码如下:
// ChildrenSelect import React, { Children, BaseHTMLAttributes, FC, cloneElement, ReactElement } from "react"; type Props = { // ChildrenSelect专属props } & BaseHTMLAttributes<HTMLDivElement>; export const ChildrenSelect: FC<Props> = ({ children }) => { const childrenArray = Children.toArray(children); const handleChildClick = (e) => { console.log(e); }; return ( <div className="children-select"> <div>{/* 其他父组件内容 */}</div> {Children.map(childrenArray, (child, i) => { console.log("child is", child); return cloneElement(child as ReactElement<ChildComponentProps>, { onClick: handleChildClick, text: `modified child ${i}`, }); })} </div> ); };
通过开发者工具能看到子组件已经被设置了新的onClick和text props,但点击子组件时handleChildClick没有触发,无日志输出。
解决方案
问题出在两个核心点:
- 子组件需正确转发
onClickprops:如果Child组件内部没有把传入的onClick绑定到可点击的DOM元素上,父组件设置的回调永远不会触发。示例Child组件写法:
const Child: FC<ChildComponentProps> = ({ onClick, id, text }) => { // 必须把onClick绑定到实际可点击的元素上 return <div onClick={onClick}>{text || id}</div>; };
- 传递子组件ID给父组件回调:要让
ChildrenSelect识别点击的子组件,克隆子组件时需把id作为参数传入点击回调,同时自动设置selected状态:
// 修改后的ChildrenSelect组件 import React, { Children, FC, cloneElement, ReactElement } from "react"; type ChildComponentProps = { id: Children; selected?: boolean; onClick?: () => void; text?: string; }; type ChildrenSelectProps = { selected: Children; onChildClick: (c: Children) => void; } & React.HTMLAttributes<HTMLDivElement>; export const ChildrenSelect: FC<ChildrenSelectProps> = ({ children, selected, onChildClick }) => { return ( <div className="children-select"> <div>{/* 其他父组件内容 */}</div> {Children.map(children, (child) => { const { id } = (child as ReactElement<ChildComponentProps>).props; // 绑定带ID的点击回调 const handleClick = () => onChildClick(id); // 自动设置选中状态 return cloneElement(child as ReactElement<ChildComponentProps>, { onClick: handleClick, selected: selected === id, }); })} </div> ); };
修改后,点击子组件时会正确触发父组件的onChildClick回调并传递对应ID,同时自动维护子组件的selected状态,完全符合期望的简化写法。
内容的提问来源于stack exchange,提问作者Marco Castanho
相关产品推荐
相关产品推荐

