React中如何正确切换两个Bootstrap日历下拉框?
解决方案:React 18 下 Bootstrap 日历下拉联动实现
核心优化思路
放弃模拟点击按钮的方式,改用 Bootstrap 官方的 Dropdown JS API 控制下拉状态,同时针对 React 18 严格模式的双重挂载特性,完善 useEffect 的清理逻辑,让组件逻辑更清晰可靠。
重构后的代码实现
1. CalendarDropdown.js 组件
import { useEffect, useRef } from 'react'; import * as bootstrap from 'bootstrap'; // 若用CDN引入则不需要此import,直接用window.bootstrap const CalendarDropdown = ({ date, onChange, open }) => { const dropdownContainerRef = useRef(null); const dropdownInstanceRef = useRef(null); const monthYear = dateHelper.dateToMonthYear(date); useEffect(() => { // 初始化Bootstrap Dropdown实例 if (dropdownContainerRef.current && !dropdownInstanceRef.current) { dropdownInstanceRef.current = new bootstrap.Dropdown(dropdownContainerRef.current, { autoClose: 'outside' }); } // 根据open prop控制下拉显隐 const instance = dropdownInstanceRef.current; if (instance) { open ? instance.show() : instance.hide(); } // 清理函数:处理React18双重挂载,销毁实例避免内存泄漏 return () => { if (dropdownInstanceRef.current) { dropdownInstanceRef.current.dispose(); dropdownInstanceRef.current = null; } }; }, [open]); const handleMonthSelect = (selectedDate) => { onChange(selectedDate); // 选择日期后自动关闭当前下拉 dropdownInstanceRef.current?.hide(); }; return ( <div className="dropdown" style={styles.dropdownButtonWrapper} ref={dropdownContainerRef}> <button style={styles.dropdownButton} className="btn btn-secondary dropdown-toggle" type="button" data-bs-toggle="dropdown" aria-expanded={open}> <span>{monthYear}</span> </button> <ul className="dropdown-menu" onClick={e => e.stopPropagation()}> <Calendar maxDetail="year" value={date} onClickMonth={handleMonthSelect} /> </ul> </div> ); };
2. App.js 父组件
import { useState } from 'react'; const App = ({ startDate, endDate, setStartDate, setEndDate }) => { const [isStartDropdownOpen, setIsStartDropdownOpen] = useState(false); const [isEndDropdownOpen, setIsEndDropdownOpen] = useState(false); const handleStartDateChange = (date) => { setStartDate(date); // 关闭第一个下拉,打开第二个 setIsStartDropdownOpen(false); setIsEndDropdownOpen(true); }; const handleEndDateChange = (date) => { setEndDate(date); setIsEndDropdownOpen(false); }; return ( <div className="input-group"> <CalendarDropdown date={startDate} onChange={handleStartDateChange} open={isStartDropdownOpen} /> <CalendarDropdown date={endDate} onChange={handleEndDateChange} open={isEndDropdownOpen} /> </div> ); };
关键优化点说明
- 用官方API替代模拟点击:直接调用 Bootstrap Dropdown 的
show()/hide()方法控制状态,避免手动点击按钮带来的不可控问题,适配 React 18 严格模式的双重挂载场景。 - 完善清理逻辑:useEffect 返回的清理函数会销毁 Dropdown 实例,防止内存泄漏,同时处理严格模式下的重复挂载。
- 简化组件通信:移除原代码中 onChange 返回值的别扭写法,父组件直接通过状态控制两个下拉的联动,逻辑更清晰。
- 正确设置 aria 属性:将按钮的
aria-expanded与openprop 绑定,符合无障碍访问规范。
内容的提问来源于stack exchange,提问作者user3015876
相关产品推荐
相关产品推荐

