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值的效果。

样式

在这里插入图片描述
在这里插入图片描述
分成两个,互不影响。
调用
在这里插入图片描述

猜你喜欢

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