本文正在参加「金石计划」
一:引言
本篇文章仿写antd组件库的Breadcrumb组件,文章重点是模拟Breadcrumb组件的内部功能实现,样式比较丑希望大家包涵。
二:组件分析
观察下图,我们设计一个可配置的分页组件,其配置项包含如下
- className 类名
- separator 分隔符
- href 跳转链接
- onClick 点击事件
三:问题拆解
在设计组件前,我们可以先考子问题,最后组装起来就实现了目标组件。
问题1:分隔符如何添加?
- 首先我们在Breadcrumb组件内部通过React.Children.map可以获取其内部传递的所有孩子节点,然后判断如果当前节点是不是最后一个孩子,则添加分隔符。
问题2:如何实现路由跳转?
- 这个我们只需要在每个BreadItem上绑定(路由地址),点击后自动实现路由跳转。
五:Breadcrumb代码实现
Bread代码
-
通过React.Children.map遍历拿到每个孩子对象节点
-
判断孩子节点如果不是最后出现,则添加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代码
-
通过props接受用户传递的参数和父组件Bread传递的参数
-
根据参数添加逻辑
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>
演示2:添加分隔符
<Bread separator='->'>
<BreadItem>首页</BreadItem>
<BreadItem>我的</BreadItem>
<BreadItem>其他</BreadItem>
</Bread>
演示3:添加路由跳转
<Bread separator='->'>
<BreadItem link="/">首页</BreadItem>
<BreadItem link="/header">我的</BreadItem>
<BreadItem>其他</BreadItem>
</Bread>
演示3:添加点击事件
<Bread>
<BreadItem onClick={(e)=>console.log(e)}>首页</BreadItem>
<BreadItem >我的</BreadItem>
<BreadItem>其他</BreadItem>
</Bread>
总结
今天Bread组件到此结束,希望大家多多支持,我们下一个组件见。