Antd Menu 简述

Menu 为页面和功能提供导航的菜单列表。

导航菜单是一个网站的灵魂,用户依赖导航在各个页面中进行跳转。一般分为顶部导航和侧边导航,顶部导航提供全局性的类目和功能,侧边导航提供多级结构来收纳和排列网站架构。

要点提取

核心实现

Menu 的用法。

  <Menu selectedKeys={["analysis"]} ><Menu.Item index="analysis">分析</Menu.Item><Menu.Item index="market">营销</Menu.Item><Menu.SubMenu index="set" title={<span>设置</span>}><Menu.Item index="4">二级标题</Menu.Item><Menu.Item index="5">二级标题</Menu.Item><Menu.SubMenu index="sub3" title={<span>二级菜单</span>}><Menu.Item  index="6">三级标题</Menu.Item></Menu.SubMenu></Menu.SubMenu></Menu>

通过上面的 Demo, 我们可以看出 Menu 组件的三个核心部分:Menu, Menu.Item, Menu.SubMenu

Menu 为一个用例中最顶层组件,SubMenu 的父组件为 SubMenu 或 Menu。MenuItem 作为叶子 (leaf) 节点,父组件为 SubMenu 或 Menu.


:自实现组件库中 mode 命名有点偏差,vertical 其实对应 antd 中的 inline

Menu.tsx

export default class Menu extends React.Component<IMenuProps, any> {static propTypes = {...}static defaultProps = {...}static Item = MenuItem;static SubMenu = SubMenu;static childContextTypes = {level: PropTypes.number,mode: PropTypes.string,onClick: PropTypes.func,onSelect: PropTypes.func,selectedKeys: PropTypes.arrayOf(PropTypes.string)};getChildContext() {return {level: 1,mode: this.props.mode,onClick: this.onClick,onSelect: this.onSelect,selectedKeys: this.state.selectedKeys};}constructor(props) {super(props);this.state = {selectedKeys: this.props.selectedKeys || []};}onSelect = (params: ClickParam) => {this.setState({selectedKeys: [params.key]});if (this.props.onSelect) {this.props.onSelect(params);}}onClick = (params: ClickParam) => {const {onClick} = this.props;if (typeof onClick === 'function') {onClick(params);}}render() {const {classPrefix,className,style,children,mode,onClick,selectedKeys,...restProps} = this.props;return (<divclassName={classNames(className, `${classPrefix}-container`, {})}ref="menus-container"style={style}{...restProps}><ulclassName={classNames(classPrefix, {[`${classPrefix}-vertical`]: mode === 'vertical',[`${classPrefix}-horizontal`]: mode === 'horizontal'})}>{children}</ul></div>);}
}

上述对 Menu 实现的要点如下:
1. 负责菜单布局:比如水平 (horizontal),垂直 (vertical),内嵌 (inline)
2. 菜单项 (MenuItem, SubMenu) 选中或点击的事件代理: 将 Menu 的 onClick 与 onSelect 事件回调作为 context 传递到子组件,在 MenuItem 或 SubMenu 的 click 事件中调用,达到一个类似于事件代理的效果
3. 菜单顶层 level = 1: Menu 作为最顶层组件,将 level 作为 context 传递到子组件,作为一个标的。SubMenu 自己的 context 中也有 level,值为父级 Level + 1。由于 MenuItem 是叶子节点,它的 level 即为父级 context.level + 1.
在React中,如果父组件和祖先组件具有相同的 context 字段名,父组件会覆盖祖先组件。因此通过level 的层层自增,我们总能取得当前节点的 level 值。

SubMenu.tsx:

import {Transition} from 'react-transition-group';
import Popover from '../Popover/Popover';
...export default class SubMenu extends React.Component<ISubMenuProps, any> {static propTypes = {...disabled: PropTypes.bool,key: PropTypes.string,expanded: PropTypes.bool}static defaultProps = {...}// 来自父组件 Menu 或 SubMenustatic contextTypes = {level: PropTypes.number};// 作为父组件传递 contextstatic childContextTypes = {level: PropTypes.number};// 可能有 subMenu -> subMenu 情况getChildContext() {return {level: this.context.level + 1};}...render() {const {...} = this.props;const {mode, level} = this.context;// Menu 为横向布局时const popMenuItems = (<ul className={`${classPrefix}-pop`} style={{minWidth: 100}}>{children && <div className={`${classPrefix}-pop-content`}>{children}</div>}</ul>);// SubMenu 本体const subMenu = (<divclassName={`${classPrefix}-title`}onClick={this.onTitleClick.bind(this)}><div className={`${classPrefix}-title-content`} style={style}>{title}{mode === 'vertical' && <Icon type={`arrow-${this.state.expanded ? 'up' : 'down'}`} className={`${classPrefix}-arrow`}/>}</div></div>);return (<li{...restProps}key={key}ref="submenu"className={classNames(classPrefix, className, {[`${classPrefix}-disabled`]: disabled,[`${classPrefix}-horizontal`]: mode === 'horizontal' && level === 1})}>{mode === 'horizontal'? <Popoverdirection={level === 1 ? 'bottom' : 'rightTop'}content={popMenuItems}className={...}trigger="hover">{subMenu}</Popover>: subMenu}<Transition ... in={this.state.expanded}><ul className={`${classPrefix}-sub`}>{children}</ul></Transition></li>);}
}

上述对 SubMenu 的实现要点如下:

  • 根据 mode 的不同有不同的实现

    • mode 为 horizontal 时使用 Popover 组件,menuItem 以 “弹出” 的形式展现(主要考虑到的交互是 SubMenu hover 时展现 MenuItem)

    • mode 为 vertical 时正常展现,不用 Popover. 展开时的动画效果由第三方控件 Transition 提供。

MenuItem.tsx:

export default class MenuItem extends React.Component<IMenuItemProps, any> {static propTypes = {...index: PropTypes.string // item 的唯一标志}static defaultProps = {...}static contextTypes = {level: PropTypes.number,mode: PropTypes.string,onClick: PropTypes.func,onSelect: PropTypes.func,selectedKeys: PropTypes.arrayOf(PropTypes.string)};
constructor(props) {super(props);this.state = {expanded: false,isSelected: false};}componentWillReceiveProps(nextProps, nextContext) {const selectedKeys = nextContext.selectedKeys;const key = this.props.index;if (selectedKeys.indexOf(key) > -1) {this.setState({isSelected: true});} else {this.setState({isSelected: false});}}componentDidMount() {const selectedKeys = this.context.selectedKeys;const key = this.props.index;if (selectedKeys.indexOf(key) > -1) {this.setState({isSelected: true});} else {this.setState({isSelected: false});}}onClickItem(e) {this.setState({isSelected: !this.state.isSelected}, () => {const {index} = this.props;const params: ClickParam = {domEvent: e,item: this,key: index,keyPath: [index]};this.context.onSelect(params);this.context.onClick(params);});}render() {const {classPrefix,className,index,style,disabled,children,...restProps} = this.props;const {isSelected, expanded} = this.state;const {mode, level} = this.context;const menuItem = (<divclassName={classNames(`${classPrefix}-content`, {[`${classPrefix}-content-selected`]: isSelected,[`${classPrefix}-vertical-content`]: mode === 'vertical',[`${classPrefix}-horizontal-content`]: mode === 'horizontal'})}style={style}>{children}</div>);return (// TODO: Tooltip is for inlineCollapsed, set visible to false tempararily<li{...restProps}className={classNames(classPrefix, className, {[`${classPrefix}-vertical`]: mode === 'vertical',[`${classPrefix}-horizontal`]: mode === 'horizontal',[`${classPrefix}-vertical-selected`]: isSelected && mode === 'vertical',[`${classPrefix}-horizontal-selected`]: isSelected && mode === 'horizontal' && level === 1,[`${classPrefix}-disabled`]: disabled})}onClick={!disabled && this.onClickItem.bind(this)}><Tooltipvisible={expanded}direction="right"message={menuItem}className={classNames(`${classPrefix}-tooltip`, {})}trigger="hover"showArrow={false}>{menuItem}</Tooltip></li>);}
}

上述对 MenuItem 的实现要点如下:

  • 实现关键在于从 Menu 或 SubMenu 组件传递的 context

    • 点击 MenuItem 时同时触发 SubMenu 或 Menu 通过 context 传递来的 click 回调

    • mode 与 level 会影响 MenuItem 的展现,这也是为什么 mode 与 level 需要通过 context 传递过来。

更多组件

更多组件自实现系列,更多文章请参考:

从Antd 源码到自我实现之 Form表单

从Antd 源码到自我实现之 Grid 栅格系统

React 实现 Modal 思路简述

从Antd 源码到自我实现之 Menu 导航菜单相关推荐

  1. 从Antd 源码到自我实现之 Form表单

    前言 Antd 中的组件大部分基于蚂蚁金服的组件库 react-component.antd 与 react-component 都是开源项目,阅读其源码可以给我们带来很多收益,比如: 了解各式各样的 ...

  2. antd源码-spin解析

    antd源码-spin解析 spin的作用是代表当前块正在加载中 Spin 元素的渲染 renderSpin = ({ getPrefixCls }: ConfigConsumerProps) =&g ...

  3. Antd源码浅析(二)InputNumber组件 一

    前言 上篇我们讲了Icon组件,Icon组件是Antd源码库中实现比较简单的组件,适合大家入门,这篇文章主要和大家一起分析一下数字输入框组件,即InputNumber,难度适中,但蕴含的Antd里较为 ...

  4. antd源码-Affix固钉解析

    antd源码-Affix固钉解析 固钉其实按照我自己的理解就是用固定定位将其定位到某个位置.很简单的一个效果. antd-affix我认为其核心可以概括为几点: 组件加载滚动监听,组件销毁销毁监听. ...

  5. php公众号交友源码_个性定制微信导航源码,PHP公众号导航源码,含手机wap版,微信数据...

    程序采用PHP5+MYSQL做为技术基础进行开发.2 z# c2 u. j" A 带数据,带手机版,PC版风格全网首发,大气. F  h; \( x- E  k7 y        程序含数 ...

  6. 导航php系统,php源码:智能的网址导航建站系统 114啦网址导航系统 v2.0

    好东西介绍: 114啦 v2.0 Beta版(2014年08月15日发布) -------------– [程序新特征] ├基于114啦官方最新模板风格--简洁.美观,网址.内容高度整合 ├采用高效Y ...

  7. 美丽乡村建设网站php源码_2020最新亲测php网址导航源码全开源-清爽收录导航网站源码...

    2020最新亲测php网址导航源码全开源-清爽收录导航网站源码 一款轻巧.清爽的php源码,已亲测完整可用,运行十分流畅,网页加载非迅速,源码UI设计漂亮,且功能完整.为响应式设计,兼容各种手机移动端 ...

  8. BootstrapBlazor实战 Menu 导航菜单使用(2)

    接上篇: B08. BootstrapBlazor实战 Menu 导航菜单使用(1) 实战BootstrapBlazorMenu 导航菜单的使用, 以及整合Freesql orm快速制作菜单项数据库后 ...

  9. ant vue 树形菜单横向显示_ant design vue menu 导航菜单

    ant design vue menu 导航菜单 ant design vue menu 导航菜单是一个网站或者系统的重要功能,通过导航可以对网站或者系统的功能进行分门别类. 水平导航菜单 例子 首页 ...

最新文章

  1. oracle学习笔记一
  2. python日志汇总
  3. 【渝粤教育】国家开放大学2018年秋季 0233-21T学前儿童语言教育 参考试题
  4. pthread vs openMP之我见
  5. CTS(4)---mtk cts FAIL处理方法
  6. 使用@Aspect切面进行让JDBC自动关闭(Spring AOP)
  7. mysql 命令连接,授权用户
  8. alitum designer 的PCB生成gerber文件步骤
  9. python将png转为jpg,Python OpenCV读取png图像转成jpg图像存储的方法
  10. 新edge保持百度账号登录
  11. x86 x64 IA64的关系和区别
  12. fisco bcos 调用接口报错WeBASE-Node-Manager user not logged in 版本:v1.5.2
  13. 中国计算机学会推荐国际学术会议和期刊目录 2015
  14. Linux下Oracle11G64位安装流程
  15. 计算机无法验证签名,电脑提示“无法验证此文件的数字签名”的修复方法
  16. 工程师的终极灵魂拷问: 谷歌和FB的offer, 应该怎么选?
  17. 【Git】push 分支报错 error: failed to push some refs to...
  18. C++ 关于protected
  19. 推荐几本经典计算机书籍
  20. 10只小白鼠1000支药水找出毒药问题

热门文章

  1. 樊登读书赋能读后感_樊登读书会本周末视频解读新书:《赋能》突破深井,打造优质团队...
  2. 【模板】高精度取余函数
  3. 蒙特卡洛方法求圆周率
  4. 亚马逊账号被关联能申诉得回来吗
  5. 【持续更新】uni-app学习笔记
  6. IDEA的 tool 之 Duplicate detector
  7. webdav同步书签-floccus
  8. 2014年3月21日51CTO微软MVP聚会照片
  9. 在线培训机构需公示教师资格证 一对一业务将最受影响
  10. 对 iOS 14.2 糟糕的音乐控制界面的思考