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)
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.data
this.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 ()
.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
解决的问题: