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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 03:06:03