【CSS】自定义下拉框

自定义下拉框

效果

在这里插入图片描述

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.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 ()

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() 方法解决。

  1. e.stopPropagation() 只能阻止合成事件间冒泡,即下层的合成事件,不会冒泡到上层的合成事件。事件本身还都是在 document 上执行。所以最多只能阻止 document 事件不能再冒泡到 window 上。
  2. Event 接口的 stopImmediatePropagation() 方法阻止监听同一事件的其他事件监听器被调用。

在 React 中,一个组件只能绑定一个同类型的事件监听器,当重复定义时,后面的监听器会覆盖之前的。

事实上 nativeEventstopImmediatePropagation只能阻止绑定在 document 上的事件监听器。而合成事件上的 e.nativeEvent.stopImmediatePropagation() 能阻止合成事件不会冒泡到 document 上。

利用 e.nativeEvent.stopImmediatePropagation 解决的问题:

  • 点击 div 内部,由于不冒泡,会正常执行菜单点击;
  • 点击 div 外部,执行 document 上事件,关闭 div;
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值