动手打造antd组件之Breadcrumb

543 阅读2分钟

本文正在参加「金石计划」

一:引言

本篇文章仿写antd组件库的Breadcrumb组件,文章重点是模拟Breadcrumb组件的内部功能实现,样式比较丑希望大家包涵。

二:组件分析

观察下图,我们设计一个可配置的分页组件,其配置项包含如下

image.png

  • className 类名
  • separator 分隔符
  • href 跳转链接
  • onClick 点击事件

三:问题拆解

在设计组件前,我们可以先考子问题,最后组装起来就实现了目标组件。

问题1:分隔符如何添加?

  • 首先我们在Breadcrumb组件内部通过React.Children.map可以获取其内部传递的所有孩子节点,然后判断如果当前节点是不是最后一个孩子,则添加分隔符。

问题2:如何实现路由跳转?

  • 这个我们只需要在每个BreadItem上绑定(路由地址),点击后自动实现路由跳转。

五:Breadcrumb代码实现

Bread代码

  1. 通过React.Children.map遍历拿到每个孩子对象节点

  2. 判断孩子节点如果不是最后出现,则添加separator分隔符号。否则不添加分隔符

     import React from "react";
     import classNames from "classnames";
     interface BreadProps {
       children?:React.ReactNode,
       className?:string,
       separator?:React.ReactNode,
     }
     const Bread = (props:BreadProps) => {
       const {children,className,separator} = props;
       const classes = classNames('bread',className);
    
       /**判断是否是最后一个breaditem组件 */
       const judgeLastBreadItem = (idx:number) => {
         //从index+1往后找
         let isLast:boolean = true;
         React.Children.forEach(children,(item:any,index)=>{
           if(index>idx && item?.type?.name==='BreadItem') isLast = false;
         })
         return isLast;
       }
    
       /*核心:处理内部包裹的BreadItem组件,包括添加separator分隔符*/
       const BreadElement = React.Children.map(children,(item :any,index:number)=>{
         if(typeof item !== "object") return item;
         if(!item?.type?.name || item?.type?.name !== 'BreadItem') {
           console.error('子组件必须是bredItem');
           return item;
         }
         if(children instanceof Array) {
           if(!judgeLastBreadItem(index)) 
             return React.cloneElement(item,{
               separator
             })
         }
         return item;
       })
       return (
         <div className={classes}>{BreadElement}</div>
       )
     }
     Bread.defaultProps = {
       separator:'>'
     }
     export default Bread;
    

BreadItem代码

  1. 通过props接受用户传递的参数和父组件Bread传递的参数

  2. 根据参数添加逻辑

    import React from "react";
    import { useNavigate } from "react-router-dom"
    import classNames from 'classnames';
    interface BreadItemProps {
      className?:string,
      children?:React.ReactNode,
      link?:string,
      separator?:React.ReactNode
      onClick?:(e:any)=>void
    }
    type BreadItemUnin = BreadItemProps & React.BaseHTMLAttributes<HTMLElement> 
    type BreadItemUnion =BreadItemUnin & React.AnchorHTMLAttributes<HTMLElement>
    type BreadItemPropsResult = Partial<BreadItemUnion>
    const BreadItem = (props:BreadItemPropsResult) => {
      const {children,className,link,onClick,separator,...reset} = props;
      const classes = classNames('bread-item',className);
      const nav = useNavigate();
    
      return (
        <div className={classes}
          {...reset}
          onClick={(e)=>{
            if(onClick) onClick(e);
            if(link) {//路由跳转
              nav(link);
            }
          }}
        >
          {children} 
          {
            separator && (
              <span className="bread-item-separator">
                {separator}
              </span>
            )
          }
        </div>
      )
    }
    
    export default BreadItem;
    

六:功能演示

演示1:默认

<Bread>
        <BreadItem>首页</BreadItem>
        <BreadItem>我的</BreadItem>
        <BreadItem>其他</BreadItem>
 </Bread>
 

image.png

演示2:添加分隔符

    <Bread separator='->'>
        <BreadItem>首页</BreadItem>
        <BreadItem>我的</BreadItem>
        <BreadItem>其他</BreadItem>
      </Bread>
      
     

image.png

演示3:添加路由跳转

<Bread separator='->'>
    <BreadItem link="/">首页</BreadItem>
    <BreadItem link="/header">我的</BreadItem>
    <BreadItem>其他</BreadItem>
 </Bread>

8.gif

演示3:添加点击事件

<Bread>
        <BreadItem onClick={(e)=>console.log(e)}>首页</BreadItem>
        <BreadItem >我的</BreadItem>
        <BreadItem>其他</BreadItem>
    </Bread>

总结

今天Bread组件到此结束,希望大家多多支持,我们下一个组件见。