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

Posted coderlin_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了react+ts 造轮子仿antd Menu组件(初级版)相关的知识,希望对你有一定的参考价值。

Menu

Menu组件大家还是比较熟悉的,可以切换菜单栏的一种,先看初级版的Menu效果图

组件分析:
样式有四种,默认是Primary,还有danger,success,warning,
然后有两种排序,上下或者左右。
点击可以切换。

接口


一共有三个,最后一个是来实现父子组件的数据共享。
第一个为Menu组件,本质上就是一个ul标签,可以传入的值当前高亮的索引,类型,样式,onSelect回调函数,类名。
第二个是MenuItem组件,本质上是一个li标签,可以传入的值有标明自身索引的值Index,disabled,类名还有style控制样式。

类型别名

Menu组件代码

import React,  useState, createContext  from 'react'
import cx from 'classnames'
import  IMenuProps, menuType, IMenuContext, IMenuItemProps  from './common'
import  MenuContext  from './conText'



const Menu: React.FC<IMenuProps> = (props) => 
    const  style, color, className, activeIndex, type, onSelect, children, ...restProps  = props
    
    const classes = cx(
        'menu', 
        className, 
           [`menu-$type`]: type,
            [`menu-$style`]:style,
            [`menu-horizontal`]:Boolean(type!=='vertical')
        )
    const [index, setindex] = useState(activeIndex)
    const handle = (index: number) => 
        setindex(index)
        if (onSelect) 
            onSelect(index)
        
    
    const value: IMenuContext = 
        activeIndex: index!, //感叹号非空,强行告诉编译器,我这个activeInedx不是空的
        onSelect: handle
    
    //代码升级
    const renderChildren = () => 
        return React.Children.map(children, (child, index) => 
            const chlidElement = child as React.FunctionComponentElement<IMenuItemProps>
            if (chlidElement?.type?.displayName !== 'MenuItem') //传入的子组件不是MenuItem就报错
                console.error('Waring: Menu has a child which is not a MenuItem')
             else 
                return React.cloneElement(chlidElement, 
                    //每次都要传入index,甚是麻烦,cloneElement,可以复制节点并且附加上属性,前提是IMenuItemProps里面有定义的
                    index
                )
            
        )
    
    return <ul className=classes ...restProps data-testid="test-menu">
        <MenuContext.Provider value=value>
            renderChildren()
        </MenuContext.Provider>
    </ul>



Menu.defaultProps = 
    activeIndex: 0,
    type: menuType.Horizontal,



export default Menu

样式用属性选择器,拼接,默认是menu-horizontal,也就是横着来排序。
使用Context来共享数据,

创建共享数据

第二个值是函数,子组件点击后会回传index回来,父组件再更新index的值,然后再调用外面传入的onSelect函数,将index值传进去。
这里用了一个函数

这个函数的主要作用是判断传进来的children是不是MenuItem,如果不是即报错,是的话,返回一个cloneElement,这个方法可以复制我们传入的childElement,并且给他附上一个属性,index,这样创建出来的li标签上都有一个index属性。不然我们调用时只能手动添加,不方便。
因为我们是通过这个index的值来判断当前是否高亮,所以他是必须的。

MenuItem组件代码

import React,  useContext  from 'react'
import cx from 'classnames'
import MenuContext  from './conText'
import IMenuItemProps from './common'

const MenuItem: React.FC<IMenuItemProps> = (props) => 
    const index, classNames, disabled, style, children, ...restProps  = props
    const value = useContext(MenuContext);
    const activeIndex, onSelect = value
    const classes = cx('menu-item',classNames,'menu-item-disabled':disabled,'menu-item-active':Boolean(activeIndex===index))
    const handleClick = () => 
        if(onSelect && !disabled &&(typeof index === 'number'))
            onSelect(index)
        
    
    return (
        <li className=classes style= style onClick=handleClick ...restProps>children</li>
    )

MenuItem.defaultProps = 
    disabled:false

MenuItem.displayName = 'MenuItem'
export default MenuItem

这里就很容易了,判断高亮只需判断父组件传入的index,与自身的index值是否相同即可。回调的话只是将index值通过父亲传入的handle也就是onSelect函数传出去,达到实时改变Index值的效果。

样式



分成两个,互不影响。
调用

以上是关于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表单