ReactJS如何实现符合DRY原则的可复用下拉筛选器,支持同组件内多次使用且取值独立
你现有代码的核心问题是两个下拉实例共用了同一组状态(columnShow、openDropdown)和同一个wrapperRef,所以任意一个下拉的操作都会同步影响另一个,天然没法独立控制。
最合理的方案是把下拉的所有交互逻辑、内部状态封装为独立的自定义组件,每个组件实例自己维护内部状态,天然实现多实例互不干扰,也完全符合DRY原则。
实现步骤
1. 封装可复用下拉组件
将点击外部关闭的逻辑、下拉打开状态、选中值状态全部放到独立组件内部,通过props接收选项、回调参数,兼容不同场景的使用需求:
import { useState, useRef, useEffect, FC } from 'react'; // 点击外部关闭的通用hook,提到组件外避免重复声明 const useOutsideAlerter = (ref: React.RefObject<HTMLElement>, onClose: () => void) => { useEffect(() => { const handleClickOutside = (event: MouseEvent) => { if (ref.current && !ref.current.contains(event.target as Node)) { onClose(); } }; document.addEventListener("mousedown", handleClickOutside); return () => document.removeEventListener("mousedown", handleClickOutside); }, [ref, onClose]); }; // 下拉组件入参定义 interface CustomDropdownProps { options: Array<{ id: string }>; // 下拉选项列表 placeholder?: string; // 未选中时的提示文字 onChange: (selectedId: string) => void; // 选中值变化时的回调,将选中值回传给父组件 } // 沿用你原有的样式组件,不用修改样式逻辑 export const CustomDropdown: FC<CustomDropdownProps> = ({ options, placeholder = "Select", onChange }) => { // 内部状态每个实例独立维护,多实例互不影响 const [selectedValue, setSelectedValue] = useState<string>(""); const [openDropdown, setOpenDropdown] = useState(false); const wrapperRef = useRef<HTMLDivElement>(null); useOutsideAlerter(wrapperRef, () => setOpenDropdown(false)); const toggleDropdown = () => setOpenDropdown(!openDropdown); const handleSelect = (id: string) => { setSelectedValue(id); setOpenDropdown(false); onChange(id); }; return ( <TableFilter> <TableFilterBlock ref={wrapperRef}> <TableFilterInput onClick={toggleDropdown}> {selectedValue || placeholder} </TableFilterInput> <TableFilterDropdown toggleDropdown={openDropdown}> {options.map((item, index) => ( <TableDropdownList key={index}> <div onClick={() => handleSelect(item.id)}> {item.id} </div> </TableDropdownList> ))} </TableFilterDropdown> </TableFilterBlock> </TableFilter> ); };
2. 父组件调用
父组件只需要分别维护两个下拉的选中值即可,不需要再处理下拉的交互逻辑,代码大幅精简:
interface TableComponentProps { columns: Array<any>; } export const TableComponent: FC<TableComponentProps> = ({ columns }) => { // 两个独立状态分别存储两个下拉的选中值,互不干扰 const [firstFilterValue, setFirstFilterValue] = useState<string>(""); const [secondFilterValue, setSecondFilterValue] = useState<string>(""); const data = useMemo(() => TableContent, []); // 下拉选项如果两个下拉共用就统一处理,需要不同选项就分别定义 const dropdownOptions = useMemo(() => columns.slice(1), [columns]); return ( <div> <CustomDropdown options={dropdownOptions} onChange={setFirstFilterValue} /> <CustomDropdown options={dropdownOptions} onChange={setSecondFilterValue} /> <Table columns={columns} data={data} hiddenColumns={firstFilterValue} // 按需传入你需要的选中值即可 /> </div> ); };
拓展说明
如果后续需要新增更多同类型下拉,只需要新增对应的选中状态,多写一行<CustomDropdown />调用即可,不需要重复写交互逻辑。如果需要不同下拉有不同的选项、提示文字,传入对应的options和placeholder参数即可,灵活度很高。
内容的提问来源于stack exchange,提问作者user13914915
相关产品推荐
相关产品推荐

