react+ts 造轮子仿antd Menu组件(升级版)

Menu

之前做了一个简单版的Menu组件,然后对这个组件升级了一下,使其支持下拉菜单功能。看效果图
在这里插入图片描述
横向的下拉菜单鼠标经过即打开,鼠标离开即关闭。纵向的下拉菜单是鼠标点击即打开,再点击一次即关闭。
继上一篇初级版后,我们需要多一个组件,SubMenu组件,这个组件的本质也是一个li标签,然后里面放着一个div和一个ul,div用来放标题,ul用来放下拉菜单的组件,所以SubMenu组件里面放着的也是MenuItem组件。
看代码:
接口
在这里插入图片描述
组件代码

import React, {
    
    useState, useContext} from 'react'
import {
    
    MenuContext}  from './conText'
import cx from 'classnames'
export interface ISubMenu {
    
    
    className?: string,
    index?: string,
    title?: string,
}

const SubMenu: React.FC<ISubMenu> = (props) => {
    
    
    const {
    
     className, index, title, children } = props
    const value = useContext(MenuContext);
    const isOpen = index? (value.defaultOpenSubMenu as Array<string>).includes(index) : false
    const [menuOpen, setmenuOpen] = useState(isOpen);
    const classes = cx('menu-item submenu-item', className, {
    
    'menu-submeu-open':menuOpen})
    const renderChildren = () => {
    
    
        const childrenElement = React.Children.map(children, (child, i) => {
    
    
            const chlidElement = child as React.FunctionComponentElement<ISubMenu>
            if (chlidElement?.type?.displayName !== 'MenuItem') {
    
    //传入的子组件不是MenuItem就报错
                console.error('Waring: Menu has a child which is not a MenuItem')
            } else {
    
    
                return React.cloneElement(chlidElement, {
    
    
                    index: `${
    
    index}-${
    
    i}`
                  })

            }
        })
        return (
            <ul className="menu-submenu">
                {
    
    childrenElement}
            </ul>
        )
    }
    const handleClick = (e: React.MouseEvent)=>{
    
     //竖向鼠标点击显示
        e.preventDefault()
        setmenuOpen(!menuOpen)
    }
    let timer:any //防抖函数
    const handleMouse = (e: React.MouseEvent, toggle: boolean) => {
    
     //横向鼠标经过即显示
        clearTimeout(timer)
        e.preventDefault()
        timer = setTimeout(()=>{
    
    
            setmenuOpen(toggle)
        },50)
    }
    const clickEvents = value.type === 'vertical'?{
    
    
        onClick:(e: React.MouseEvent)=>{
    
    handleClick(e)}
    }:{
    
    }
    const MouseEvents = value.type !== 'vertical'?{
    
    
        onMouseEnter:(e: React.MouseEvent)=>{
    
    handleMouse(e, true)},
        onMouseLeave:(e: React.MouseEvent)=>{
    
    handleMouse(e, false)}
    }: {
    
    }
    return (
        <li key={
    
    index} className={
    
    classes} {
    
    ...MouseEvents}>
            <div className="submenu-title" {
    
    ...clickEvents}>{
    
    title}</div>
            {
    
    renderChildren()}
        </li>
    )
}

SubMenu.displayName = 'SubMenu'
export default SubMenu

一步一步看,首先先看结构
在这里插入图片描述
本质也是一个li标签,再看在这里插入图片描述
这个跟我们初级版的也是一样的,因为我们要判断SubMenu里面包含的是不是MenuItem标签,并且必须手动给他加上index值,这个index值是返回给回调函数的并且用于判断当前这个MenuItem是不是处于active状态。由于我们外层用了索引值Index来做index的值,所以如果在这里继续用Index值的话,将会与外面冲突。所以我们让Index值为string,并且传入另一种数据结构的index值给他,在这里插入图片描述
第一个index是外层Menu时,给每个儿子加上的index索引值,i是SubMenu遍历他的子组件时的index值,换种写法而已。所以我们下拉菜单的索引值会变成2-1,2-2等这种。

接着是点击或者经过显示下拉菜单的方法

在这里插入图片描述
先写两个函数,分别对应点击显示和鼠标经过显示。设置一个useState值,用来判断下拉菜单是否关闭。鼠标经过时,当我们鼠标高速移动时容易造成事件多次触发,所以采用了防抖函数的思维,创建一个timer。用来判断。
然后就是判断当为纵向时,点击事件成立,当不为纵向时,即横向时,鼠标事件成立。然后通过解构赋值,鼠标经过事件放在外层li上,点击事件放在div上,这样,当type为横向时,只有鼠标事件有效,当type为纵向时,只有点击事件有效。

设置默认打开

下拉菜单可能有多个,而且有的是要默认打开的,可以这样设置,
通过用户传入的一个数组,在通过判断当前下拉菜单的索引值是否在这个数组里面,在的话就默认显示,不在的话就默认不显示。这个数组由用户传给Menu组件,Menu组件通过context来共享,看代码
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
刚才说过了这个index,是Menu组件遍历子组件是自动加上去的,然后通过共享context的数据拿到数组,注意我们定义的时候用了?在这里插入图片描述
即它的值可能为undefined,所以我们必须做类型断言,告诉编译器我们这个东西就是一个字符串数组,这样编写代码的时候就不会报错,通过判断Index是否在这个数组里面,在一开始设置是否默认打开的时候,根据这个判断来设置,如果是的话一开始就为true,默认打开。
Menu组件的基本功能就算完成了,后续自己可以根据需求或者思路来加上不同的多样的功能。

猜你喜欢

转载自blog.csdn.net/lin_fightin/article/details/113516257