自定义下拉框
效果
index.tsx
import {useState, useEffect} from 'react'import {observer} from 'mobx-react-lite'import styles from './index.module.scss'import recordStore from 'src/store/recordStore'const DropDownBox: React.FC = props => {// 查询月份下拉框const [dropDown, setDropDown] = useState(false)// 默认下拉框为关闭状态const handleOpen = (e: any) => {e.stopPropagation()// 禁止事件冒泡e.nativeEvent.stopImmediatePropagation()// 阻止监听同一事件的其他事件监听器被调用setDropDown(!dropDown)// 将下拉框设为打开状态}useEffect(() => {// 点击空白处关闭下拉框document.body.addEventListener('click', () => {setDropDown(false)})return () => {document.body.removeEventListener('click', () => {setDropDown(false)})}}, [])const selectMonth = (e: any) => {// console.log(e.currentTarget.getAttribute('data-name'))setDropDown(!dropDown)// 选择月份后自动关闭下拉框const month = e.currentTarget.getAttribute('data-name')// 获取下拉选项中的属性值recordStore.setSelectedMonth(month)// 更新mobx中的month状态值}// 获取最近月份记录useEffect(() => {if (recordStore.selectedMonth) {recordStore.getDataList()}}, [recordStore.selectedMonth])// 监听月份值变化, 月份更新则触发获取该月份数据记录的方法return (<div className={styles.select}><ul><li><div className={styles.head} onClick={handleOpen}><span>{recordStore.selectedMonth}</span><span className={styles.icon_down}></span></div><ul className={dropDown ? styles.option : styles.hide}>{recordStore.monthList &&recordStore.monthList.map((item: string, index: number) => (<li key={`month_${index}`} data-name={item} className={styles.item} onClick={selectMonth}>{item}</li>))}</ul></li></ul></div>)export default observer(DropDownBox)
recordStore.ts
import {makeAutoObservable, runInAction} from 'mobx'import {formatMonth} from 'src/utils'import {listMonth} from 'src/services/api'class RecordStore {selectedMonth = '' // 选中月份monthList: Array<string> = [] // 月份列表constructor() {makeAutoObservable(this)}// 更改选中月份async setSelectedMonth(month = '') {runInAction(() => {this.selectedMonth = month})}// 获取月份列表async getMonthList() {const res = await listMonth()if (res.status === 0) {runInAction(() => {this.monthList = res.datathis.selectedMonth = res.data.length > 0 && res.data[0]})}}// 获取该月份数据记录async getDataList() {const res = await helpLog({month: formatMonth(this.selectedMonth)})if (res.status === 0) {runInAction(() => {this.dataList = res.data})}}}export default new RecordStore ()
index.module.scss
.search {display: flex;line-height: 24px;ul {margin-left: 8px;width: 96px;height: 24px;border: 1px solid #dcdcdc;border-radius: 5px;background: #eeeeee;font-size: 12px;cursor: pointer;li {position: relative;.head {overflow: hidden;width: 100%;height: 24px;box-sizing: border-box;line-height: 20px;padding-left: 15px;.icon_down {position: absolute;top: 8px;right: 16px;width: 11px;height: 7px;background-image: url(../imgs/icon_down.png);background-size: 100%;}}.option {width: 96px;position: absolute;top: 23px;left: -9px;border: none;border-radius: 0;box-sizing: border-box;z-index: 1;.item {line-height: 20px;padding-left: 18px;background: #eeeeee;height: 24px;&:hover {background: #cccccc;}}}.hide {display: none;}}}}
补充
在 React 合成事件中,需要阻止冒泡时,可以使用e.stopPropagation()
或e.preventDefault()
方法来解决,另外还可以使用e.nativeEvent.stopImmediatePropagation()
方法解决。
e.stopPropagation()
只能阻止合成事件间冒泡,即下层的合成事件,不会冒泡到上层的合成事件。事件本身还都是在document
上执行。所以最多只能阻止document
事件不能再冒泡到window
上。Event
接口的stopImmediatePropagation()
方法阻止监听同一事件的其他事件监听器被调用。
在 React 中,一个组件只能绑定一个同类型的事件监听器,当重复定义时,后面的监听器会覆盖之前的。
事实上
nativeEvent
的stopImmediatePropagation
只能阻止绑定在document
上的事件监听器。而合成事件上的e.nativeEvent.stopImmediatePropagation()
能阻止合成事件不会冒泡到document
上。
利用e.nativeEvent.stopImmediatePropagation
解决的问题:
点击 div 内部,由于不冒泡,会正常执行菜单点击;点击 div 外部,执行 document 上事件,关闭 div;