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

Posted coderlin_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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组件的基本功能就算完成了,后续自己可以根据需求或者思路来加上不同的多样的功能。

以上是关于react+ts 造轮子仿antd Menu组件(升级版)的主要内容,如果未能解决你的问题,请参考以下文章

react+ts仿antd icon库

Antd Menu组件应该如何结合react-router Link组件?

react+ts 项目:引入antd select组件联动 ,ts7053ts2741报错解决

React + TS 封装密码强度组件

二次封装antd-mobile组件库

React+Ts+Antd实现Modal弹框中控制多个Tab页的多个Form表单