From 0a0f4da6ab6e1679979795621dc9169140e55ce0 Mon Sep 17 00:00:00 2001 From: guifu Date: Tue, 13 Mar 2018 10:55:37 +0800 Subject: [PATCH 1/3] perf: refactor to avoid unnecessary re-render with mini-store --- package.json | 1 + src/Menu.jsx | 39 ++++++++++++------- src/MenuItem.jsx | 31 ++++++++------- src/MenuMixin.js | 91 ++++++++++++++++++++++++------------------- src/SubMenu.jsx | 85 ++++++++++++++++++++++++++++------------ src/SubPopupMenu.js | 24 +++++++++++- src/util.js | 21 ++++++++++ tests/SubMenu.spec.js | 27 +++++++------ 8 files changed, 209 insertions(+), 110 deletions(-) diff --git a/package.json b/package.json index 30148908..407f9fd4 100644 --- a/package.json +++ b/package.json @@ -77,6 +77,7 @@ "classnames": "2.x", "create-react-class": "^15.5.2", "dom-scroll-into-view": "1.x", + "mini-store": "^1.0.2", "prop-types": "^15.5.6", "rc-animate": "2.x", "rc-trigger": "^2.3.0", diff --git a/src/Menu.jsx b/src/Menu.jsx index 771c3cd7..3356b9f8 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -1,7 +1,8 @@ -// import React from 'react'; +import React from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; -import MenuMixin from './MenuMixin'; +import { Provider, create } from 'mini-store'; +import { default as MenuMixin, getActiveKey } from './MenuMixin'; import { noop } from './util'; const Menu = createReactClass({ @@ -59,20 +60,24 @@ const Menu = createReactClass({ if ('openKeys' in props) { openKeys = props.openKeys || []; } - return { + + this.store = create({ selectedKeys, openKeys, - }; + activeKey: { '0-menu-': getActiveKey(props, props.activeKey) }, + }); + + return {}; }, componentWillReceiveProps(nextProps) { if ('selectedKeys' in nextProps) { - this.setState({ + this.store.setState({ selectedKeys: nextProps.selectedKeys || [], }); } if ('openKeys' in nextProps) { - this.setState({ + this.store.setState({ openKeys: nextProps.openKeys || [], }); } @@ -82,7 +87,7 @@ const Menu = createReactClass({ const props = this.props; if (props.selectable) { // root menu - let selectedKeys = this.state.selectedKeys; + let selectedKeys = this.store.getState().selectedKeys; const selectedKey = selectInfo.key; if (props.multiple) { selectedKeys = selectedKeys.concat([selectedKey]); @@ -90,7 +95,7 @@ const Menu = createReactClass({ selectedKeys = [selectedKey]; } if (!('selectedKeys' in props)) { - this.setState({ + this.store.setState({ selectedKeys, }); } @@ -107,7 +112,7 @@ const Menu = createReactClass({ onOpenChange(event) { const props = this.props; - const openKeys = this.state.openKeys.concat(); + const openKeys = this.store.getState().openKeys.concat(); let changed = false; const processSingle = (e) => { let oneChanged = false; @@ -133,7 +138,7 @@ const Menu = createReactClass({ } if (changed) { if (!('openKeys' in this.props)) { - this.setState({ openKeys }); + this.store.setState({ openKeys }); } props.onOpenChange(openKeys); } @@ -142,14 +147,14 @@ const Menu = createReactClass({ onDeselect(selectInfo) { const props = this.props; if (props.selectable) { - const selectedKeys = this.state.selectedKeys.concat(); + const selectedKeys = this.store.getState().selectedKeys.concat(); const selectedKey = selectInfo.key; const index = selectedKeys.indexOf(selectedKey); if (index !== -1) { selectedKeys.splice(index, 1); } if (!('selectedKeys' in props)) { - this.setState({ + this.store.setState({ selectedKeys, }); } @@ -176,7 +181,7 @@ const Menu = createReactClass({ lastOpenSubMenu() { let lastOpen = []; - const { openKeys } = this.state; + const { openKeys } = this.store.getState(); if (openKeys.length) { lastOpen = this.getFlatInstanceArray().filter((c) => { return c && openKeys.indexOf(c.props.eventKey) !== -1; @@ -189,7 +194,7 @@ const Menu = createReactClass({ if (!c) { return null; } - const state = this.state; + const state = this.store.getState(); const extraProps = { openKeys: state.openKeys, selectedKeys: state.selectedKeys, @@ -201,7 +206,11 @@ const Menu = createReactClass({ render() { const props = { ...this.props }; props.className += ` ${props.prefixCls}-root`; - return this.renderRoot(props); + return ( + + {this.renderRoot(props)} + + ); }, }); diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index d8d64af1..0f67926b 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -3,7 +3,8 @@ import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import KeyCode from 'rc-util/lib/KeyCode'; import classNames from 'classnames'; -import { noop } from './util'; +import { connect } from 'mini-store'; +import { noop, getMenuIdFromItemEventKey } from './util'; /* eslint react/no-is-mounted:0 */ @@ -43,6 +44,13 @@ const MenuItem = createReactClass({ } }, + componentDidMount() { + // invoke customized ref to expose component to mixin + if (this.props.manualRef) { + this.props.manualRef(this); + } + }, + onKeyDown(e) { const keyCode = e.keyCode; if (keyCode === KeyCode.ENTER) { @@ -76,8 +84,7 @@ const MenuItem = createReactClass({ }, onClick(e) { - const { eventKey, multiple, onClick, onSelect, onDeselect } = this.props; - const selected = this.isSelected(); + const { eventKey, multiple, onClick, onSelect, onDeselect, isSelected } = this.props; const info = { key: eventKey, keyPath: [eventKey], @@ -86,12 +93,12 @@ const MenuItem = createReactClass({ }; onClick(info); if (multiple) { - if (selected) { + if (isSelected) { onDeselect(info); } else { onSelect(info); } - } else if (!selected) { + } else if (!isSelected) { onSelect(info); } }, @@ -112,16 +119,11 @@ const MenuItem = createReactClass({ return `${this.getPrefixCls()}-disabled`; }, - isSelected() { - return this.props.selectedKeys.indexOf(this.props.eventKey) !== -1; - }, - render() { const props = this.props; - const selected = this.isSelected(); const className = classNames(this.getPrefixCls(), props.className, { [this.getActiveClassName()]: !props.disabled && props.active, - [this.getSelectedClassName()]: selected, + [this.getSelectedClassName()]: props.isSelected, [this.getDisabledClassName()]: props.disabled, }); const attrs = { @@ -129,7 +131,7 @@ const MenuItem = createReactClass({ title: props.title, className, role: 'menuitem', - 'aria-selected': selected, + 'aria-selected': props.isSelected, 'aria-disabled': props.disabled, }; let mouseEvent = {}; @@ -160,4 +162,7 @@ const MenuItem = createReactClass({ MenuItem.isMenuItem = 1; -export default MenuItem; +export default connect(({ activeKey, selectedKeys }, { eventKey }) => ({ + active: activeKey[getMenuIdFromItemEventKey(eventKey)] === eventKey, + isSelected: selectedKeys.findIndex((k) => k === eventKey) !== -1, +}))(MenuItem); diff --git a/src/MenuMixin.js b/src/MenuMixin.js index f1515871..ef418d2b 100644 --- a/src/MenuMixin.js +++ b/src/MenuMixin.js @@ -15,7 +15,17 @@ function allDisabled(arr) { return arr.every(c => !!c.props.disabled); } -function getActiveKey(props, originalActiveKey) { +function updateActiveKey(store, menuId, activeKey) { + const state = store.getState(); + store.setState({ + activeKey: { + ...state.activeKey, + [menuId]: activeKey, + }, + }); +} + +export function getActiveKey(props, originalActiveKey) { let activeKey = originalActiveKey; const { children, eventKey } = props; if (activeKey) { @@ -80,38 +90,34 @@ const MenuMixin = { }; }, - getInitialState() { - const props = this.props; - return { - activeKey: getActiveKey(props, props.activeKey), - }; - }, - componentWillReceiveProps(nextProps) { - let props; + let activeKey; if ('activeKey' in nextProps) { - props = { - activeKey: getActiveKey(nextProps, nextProps.activeKey), - }; + activeKey = getActiveKey(nextProps, nextProps.activeKey); + updateActiveKey(this.getStore(), this.getEventKey(), activeKey); } else { - const originalActiveKey = this.state.activeKey; - const activeKey = getActiveKey(nextProps, originalActiveKey); + const originalActiveKey = this.getStore().getState().activeKey[this.getEventKey()]; + activeKey = getActiveKey(nextProps, originalActiveKey); // fix: this.setState(), parent.render(), if (activeKey !== originalActiveKey) { - props = { - activeKey, - }; + updateActiveKey(this.getStore(), this.getEventKey(), activeKey); } } - if (props) { - this.setState(props); - } }, shouldComponentUpdate(nextProps) { return this.props.visible || nextProps.visible; }, + componentDidUpdate() { + if (this.activeItem) { + scrollIntoView(ReactDOM.findDOMNode(this.activeItem), ReactDOM.findDOMNode(this), { + onlyScrollIfNeeded: true, + }); + this.activeItem = undefined; + } + }, + componentWillMount() { this.instanceArray = []; }, @@ -134,32 +140,35 @@ const MenuMixin = { } if (activeItem) { e.preventDefault(); - this.setState({ - activeKey: activeItem.props.eventKey, - }, () => { - scrollIntoView(ReactDOM.findDOMNode(activeItem), ReactDOM.findDOMNode(this), { - onlyScrollIfNeeded: true, - }); - // https://github.com/react-component/menu/commit/9899a9672f6f028ec3cdf773f1ecea5badd2d33e - if (typeof callback === 'function') { - callback(activeItem); - } - }); + updateActiveKey(this.getStore(), this.getEventKey(), activeItem.props.eventKey); + + this.activeItem = activeItem; + if (typeof callback === 'function') { + callback(activeItem); + } + return 1; } else if (activeItem === undefined) { e.preventDefault(); - this.setState({ - activeKey: null, - }); + updateActiveKey(this.getStore(), this.getEventKey(), null); return 1; } }, onItemHover(e) { const { key, hover } = e; - this.setState({ - activeKey: hover ? key : null, - }); + updateActiveKey(this.getStore(), this.getEventKey(), hover ? key : null); + }, + + getEventKey() { + // when eventKey not available ,it's menu and return menu id '0-menu-' + return this.props.eventKey || '0-menu-'; + }, + + getStore() { + const store = this.store || this.props.store; + + return store; }, getFlatInstanceArray() { @@ -182,7 +191,7 @@ const MenuMixin = { }, renderCommonMenuItem(child, i, subIndex, extraProps) { - const state = this.state; + const state = this.getStore().getState(); const props = this.props; const key = getKeyFromChildrenIndex(child, props.eventKey, i); const childProps = child.props; @@ -195,7 +204,8 @@ const MenuMixin = { rootPrefixCls: props.prefixCls, index: i, parentMenu: this, - ref: childProps.disabled ? undefined : + // customized ref function, need to be invoked manually in child's componentDidMount + manualRef: childProps.disabled ? undefined : createChainedFunction(child.ref, saveRef.bind(this, i, subIndex)), eventKey: key, active: !childProps.disabled && isActive, @@ -219,7 +229,6 @@ const MenuMixin = { }, renderRoot(props) { - this.instanceArray = []; const className = classNames( props.prefixCls, props.className, @@ -255,7 +264,7 @@ const MenuMixin = { step(direction) { let children = this.getFlatInstanceArray(); - const activeKey = this.state.activeKey; + const activeKey = this.getStore().getState().activeKey[this.getEventKey()]; const len = children.length; if (!len) { return null; diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index f6aa122d..e81766b6 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -5,9 +5,15 @@ import createReactClass from 'create-react-class'; import Trigger from 'rc-trigger'; import KeyCode from 'rc-util/lib/KeyCode'; import classNames from 'classnames'; +import { connect } from 'mini-store'; import SubPopupMenu from './SubPopupMenu'; import placements from './placements'; -import { noop, loopMenuItemRecusively } from './util'; +import { + noop, + loopMenuItemRecusively, + getMenuIdFromItemEventKey, + getMenuIdFromSubMenuEventKey, +} from './util'; let guid = 0; @@ -18,6 +24,17 @@ const popupPlacementMap = { 'vertical-right': 'leftTop', }; +const updateDefaultActiveFirst = (store, eventKey, defaultActiveFirst) => { + const menuId = getMenuIdFromSubMenuEventKey(eventKey); + const state = store.getState(); + store.setState({ + defaultActiveFirst: { + ...state.defaultActiveFirst, + [menuId]: defaultActiveFirst, + }, + }); +}; + const SubMenu = createReactClass({ displayName: 'SubMenu', @@ -43,6 +60,7 @@ const SubMenu = createReactClass({ onTitleMouseEnter: PropTypes.func, onTitleMouseLeave: PropTypes.func, onTitleClick: PropTypes.func, + isOpen: PropTypes.bool, }, isRootMenu: false, @@ -60,18 +78,32 @@ const SubMenu = createReactClass({ getInitialState() { this.isSubMenu = 1; - return { - defaultActiveFirst: false, - }; + const props = this.props; + const store = props.store; + const eventKey = props.eventKey; + const defaultActiveFirst = store.getState().defaultActiveFirst; + let value = false; + + if (defaultActiveFirst) { + value = defaultActiveFirst[eventKey]; + } + + updateDefaultActiveFirst(store, eventKey, value); + + return {}; }, componentDidMount() { this.componentDidUpdate(); + // invoke customized ref to expose component to mixin + if (this.props.manualRef) { + this.props.manualRef(this); + } }, componentDidUpdate() { const { mode, parentMenu } = this.props; - if (mode !== 'horizontal' || !parentMenu.isRootMenu || !this.isOpen()) { + if (mode !== 'horizontal' || !parentMenu.isRootMenu || !this.props.isOpen) { return; } this.minWidthTimeout = setTimeout(() => { @@ -106,13 +138,14 @@ const SubMenu = createReactClass({ onKeyDown(e) { const keyCode = e.keyCode; const menu = this.menuInstance; - const isOpen = this.isOpen(); + const { + isOpen, + store, + } = this.props; if (keyCode === KeyCode.ENTER) { this.onTitleClick(e); - this.setState({ - defaultActiveFirst: true, - }); + updateDefaultActiveFirst(store, this.props.eventKey, true); return true; } @@ -121,9 +154,8 @@ const SubMenu = createReactClass({ menu.onKeyDown(e); } else { this.triggerOpenChange(true); - this.setState({ - defaultActiveFirst: true, - }); + // need to update current menu's defaultActiveFirst value + updateDefaultActiveFirst(store, this.props.eventKey, true); } return true; } @@ -155,10 +187,8 @@ const SubMenu = createReactClass({ }, onMouseEnter(e) { - const { eventKey: key, onMouseEnter } = this.props; - this.setState({ - defaultActiveFirst: false, - }); + const { eventKey: key, onMouseEnter, store } = this.props; + updateDefaultActiveFirst(store, this.props.eventKey, true); onMouseEnter({ key, domEvent: e, @@ -212,10 +242,8 @@ const SubMenu = createReactClass({ if (props.triggerSubMenuAction === 'hover') { return; } - this.triggerOpenChange(!this.isOpen(), 'click'); - this.setState({ - defaultActiveFirst: false, - }); + this.triggerOpenChange(!props.isOpen, 'click'); + updateDefaultActiveFirst(props.store, this.props.eventKey, true); }, onSubMenuClick(info) { @@ -251,6 +279,7 @@ const SubMenu = createReactClass({ }, saveMenuInstance(c) { + // children menu instance this.menuInstance = c; }, @@ -295,7 +324,7 @@ const SubMenu = createReactClass({ const props = this.props; const baseProps = { mode: props.mode === 'horizontal' ? 'vertical' : props.mode, - visible: this.isOpen(), + visible: this.props.isOpen, level: props.level + 1, inlineIndent: props.inlineIndent, focusable: false, @@ -313,11 +342,12 @@ const SubMenu = createReactClass({ subMenuCloseDelay: props.subMenuCloseDelay, forceSubMenuRender: props.forceSubMenuRender, triggerSubMenuAction: props.triggerSubMenuAction, - defaultActiveFirst: this.state.defaultActiveFirst, + defaultActiveFirst: props.store.getState() + .defaultActiveFirst[getMenuIdFromSubMenuEventKey(props.eventKey)], multiple: props.multiple, prefixCls: props.rootPrefixCls, id: this._menuId, - ref: this.saveMenuInstance, + manualRef: this.saveMenuInstance, }; return {children}; }, @@ -328,7 +358,7 @@ const SubMenu = createReactClass({ render() { const props = this.props; - const isOpen = this.isOpen(); + const isOpen = props.isOpen; const prefixCls = this.getPrefixCls(); const isInlineMode = props.mode === 'inline'; const className = classNames(prefixCls, `${prefixCls}-${props.mode}`, { @@ -392,6 +422,7 @@ const SubMenu = createReactClass({ props.parentMenu.props.getPopupContainer : triggerNode => triggerNode.parentNode; const popupPlacement = popupPlacementMap[props.mode]; const popupClassName = props.mode === 'inline' ? '' : props.popupClassName; + return (
  • {isInlineMode && title} @@ -421,4 +452,8 @@ const SubMenu = createReactClass({ SubMenu.isSubMenu = 1; -export default SubMenu; +export default connect(({ openKeys, activeKey }, { eventKey }) => ({ + // k is of type integer, and eventKey is of type string + isOpen: openKeys.findIndex((k) => k === eventKey) > -1, + active: activeKey[getMenuIdFromItemEventKey(eventKey)] === eventKey, +}))(SubMenu); diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index 56b81c81..f05ab08b 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -2,7 +2,8 @@ import React from 'react'; import PropTypes from 'prop-types'; import createReactClass from 'create-react-class'; import Animate from 'rc-animate'; -import MenuMixin from './MenuMixin'; +import { connect } from 'mini-store'; +import { default as MenuMixin, getActiveKey } from './MenuMixin'; const SubPopupMenu = createReactClass({ displayName: 'SubPopupMenu', @@ -22,6 +23,25 @@ const SubPopupMenu = createReactClass({ mixins: [MenuMixin], + getInitialState() { + const props = this.props; + props.store.setState({ + activeKey: { + ...props.store.getState().activeKey, + [props.eventKey]: getActiveKey(props, props.activeKey), + }, + }); + + return {}; + }, + + componentDidMount() { + // invoke customized ref to expose component to mixin + if (this.props.manualRef) { + this.props.manualRef(this); + } + }, + onDeselect(selectInfo) { this.props.onDeselect(selectInfo); }, @@ -96,4 +116,4 @@ const SubPopupMenu = createReactClass({ }, }); -export default SubPopupMenu; +export default connect()(SubPopupMenu); diff --git a/src/util.js b/src/util.js index acf73f9b..f1bbda11 100644 --- a/src/util.js +++ b/src/util.js @@ -8,6 +8,27 @@ export function getKeyFromChildrenIndex(child, menuEventKey, index) { return child.key || `${prefix}item_${index}`; } +/* + find which menu an item with particular event that it belongs to. + e.g.: + eventkey of '1' or '2' belongs to root menu ('0-menu-') + eventkey of '2-1' or '2-2' belongs to sub menu with key id '2-menu-' + eventkey of '2-2-1' or '2-2-2' belongs to sub menu with key id '2-2-menu-' +*/ +export function getMenuIdFromItemEventKey(eventkey) { + const index = eventkey.lastIndexOf('-'); + if (index === -1) { + return '0-menu-'; + } + + const ret = eventkey.slice(0, index); + return `${ret}-menu-`; +} + +export function getMenuIdFromSubMenuEventKey(eventKey) { + return `${eventKey}-menu-`; +} + export function loopMenuItem(children, cb) { let index = -1; React.Children.forEach(children, (c) => { diff --git a/tests/SubMenu.spec.js b/tests/SubMenu.spec.js index 455c7762..f89697e3 100644 --- a/tests/SubMenu.spec.js +++ b/tests/SubMenu.spec.js @@ -16,10 +16,10 @@ describe('SubMenu', () => { return ( - 1 + 1 - 2 + 2 ); @@ -34,20 +34,20 @@ describe('SubMenu', () => { ); wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); - expect(wrapper.state('openKeys')).toEqual([]); + expect(wrapper.instance().store.getState().openKeys).toEqual([]); }); - describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are ture', () => { + describe('openSubMenuOnMouseEnter and closeSubMenuOnMouseLeave are true', () => { it('toggles when mouse enter and leave', () => { const wrapper = mount(createMenu()); wrapper.find('.rc-menu-submenu-title').first().simulate('mouseEnter'); jest.runAllTimers(); - expect(wrapper.state('openKeys')).toEqual(['s1']); + expect(wrapper.instance().store.getState().openKeys).toEqual(['s1']); wrapper.find('.rc-menu-submenu-title').first().simulate('mouseLeave'); jest.runAllTimers(); - expect(wrapper.state('openKeys')).toEqual([]); + expect(wrapper.instance().store.getState().openKeys).toEqual([]); }); }); @@ -62,10 +62,10 @@ describe('SubMenu', () => { it('toggles when mouse click', () => { wrapper.find('.rc-menu-submenu-title').first().simulate('click'); - expect(wrapper.state('openKeys')).toEqual(['s1']); + expect(wrapper.instance().store.getState().openKeys).toEqual(['s1']); wrapper.find('.rc-menu-submenu-title').first().simulate('click'); - expect(wrapper.state('openKeys')).toEqual([]); + expect(wrapper.instance().store.getState().openKeys).toEqual([]); }); }); @@ -112,14 +112,13 @@ describe('SubMenu', () => { describe('left & right key', () => { it('toggles menu', () => { - const wrapper = mount(createMenu()); + const wrapper = mount(createMenu({ defaultActiveFirst: true })); const title = wrapper.find('.rc-menu-submenu-title').first(); title.simulate('mouseEnter').simulate('keyDown', { keyCode: KeyCode.LEFT }); - expect(wrapper.state('openKeys')).toEqual([]); + expect(wrapper.instance().store.getState().openKeys).toEqual([]); title.simulate('keyDown', { keyCode: KeyCode.RIGHT }); - expect(wrapper.state('openKeys')).toEqual(['s1']); - + expect(wrapper.instance().store.getState().openKeys).toEqual(['s1']); expect(wrapper.find('MenuItem').first().props().active).toBe(true); }); }); @@ -148,7 +147,7 @@ describe('SubMenu', () => { wrapper.update(); wrapper.find('MenuItem').first().simulate('click'); - expect(handleSelect.mock.calls[0][0].key).toBe('1'); + expect(handleSelect.mock.calls[0][0].key).toBe('s1-1'); }); it('fires deselect event for multiple menu', () => { @@ -165,6 +164,6 @@ describe('SubMenu', () => { wrapper.find('MenuItem').first().simulate('click'); wrapper.find('MenuItem').first().simulate('click'); - expect(handleDeselect.mock.calls[0][0].key).toBe('1'); + expect(handleDeselect.mock.calls[0][0].key).toBe('s1-1'); }); }); From 735830366d0d73c618bbc6c9c9a87675a66db934 Mon Sep 17 00:00:00 2001 From: guifu Date: Tue, 27 Mar 2018 09:46:07 +0800 Subject: [PATCH 2/3] cr-comments --- src/MenuItem.jsx | 2 +- src/SubMenu.jsx | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index 0f67926b..69161b64 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -164,5 +164,5 @@ MenuItem.isMenuItem = 1; export default connect(({ activeKey, selectedKeys }, { eventKey }) => ({ active: activeKey[getMenuIdFromItemEventKey(eventKey)] === eventKey, - isSelected: selectedKeys.findIndex((k) => k === eventKey) !== -1, + isSelected: selectedKeys.indexOf(eventKey) !== -1, }))(MenuItem); diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index e81766b6..2bb3d186 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -453,7 +453,6 @@ const SubMenu = createReactClass({ SubMenu.isSubMenu = 1; export default connect(({ openKeys, activeKey }, { eventKey }) => ({ - // k is of type integer, and eventKey is of type string - isOpen: openKeys.findIndex((k) => k === eventKey) > -1, + isOpen: openKeys.indexOf(eventKey) > -1, active: activeKey[getMenuIdFromItemEventKey(eventKey)] === eventKey, }))(SubMenu); From b5d2f7264f8ad6cfab063cccacccbc28ec303dc6 Mon Sep 17 00:00:00 2001 From: guifu Date: Tue, 27 Mar 2018 23:39:41 +0800 Subject: [PATCH 3/3] fix: remove dependency on key name --- src/Menu.jsx | 3 ++- src/MenuItem.jsx | 6 +++--- src/MenuItemGroup.jsx | 2 +- src/MenuMixin.js | 18 ++++++++---------- src/SubMenu.jsx | 5 ++--- src/SubPopupMenu.js | 3 ++- src/util.js | 17 ----------------- 7 files changed, 18 insertions(+), 36 deletions(-) diff --git a/src/Menu.jsx b/src/Menu.jsx index 3356b9f8..d9ff7750 100644 --- a/src/Menu.jsx +++ b/src/Menu.jsx @@ -190,7 +190,7 @@ const Menu = createReactClass({ return lastOpen[0]; }, - renderMenuItem(c, i, subIndex) { + renderMenuItem(c, i, subIndex, subMenuKey) { if (!c) { return null; } @@ -199,6 +199,7 @@ const Menu = createReactClass({ openKeys: state.openKeys, selectedKeys: state.selectedKeys, triggerSubMenuAction: this.props.triggerSubMenuAction, + subMenuKey, }; return this.renderCommonMenuItem(c, i, subIndex, extraProps); }, diff --git a/src/MenuItem.jsx b/src/MenuItem.jsx index 69161b64..bd7bedfe 100644 --- a/src/MenuItem.jsx +++ b/src/MenuItem.jsx @@ -4,7 +4,7 @@ import createReactClass from 'create-react-class'; import KeyCode from 'rc-util/lib/KeyCode'; import classNames from 'classnames'; import { connect } from 'mini-store'; -import { noop, getMenuIdFromItemEventKey } from './util'; +import { noop } from './util'; /* eslint react/no-is-mounted:0 */ @@ -162,7 +162,7 @@ const MenuItem = createReactClass({ MenuItem.isMenuItem = 1; -export default connect(({ activeKey, selectedKeys }, { eventKey }) => ({ - active: activeKey[getMenuIdFromItemEventKey(eventKey)] === eventKey, +export default connect(({ activeKey, selectedKeys }, { eventKey, subMenuKey }) => ({ + active: activeKey[subMenuKey] === eventKey, isSelected: selectedKeys.indexOf(eventKey) !== -1, }))(MenuItem); diff --git a/src/MenuItemGroup.jsx b/src/MenuItemGroup.jsx index 813eb7f1..c6f189c9 100644 --- a/src/MenuItemGroup.jsx +++ b/src/MenuItemGroup.jsx @@ -19,7 +19,7 @@ const MenuItemGroup = createReactClass({ renderInnerMenuItem(item, subIndex) { const { renderMenuItem, index } = this.props; - return renderMenuItem(item, index, subIndex); + return renderMenuItem(item, index, subIndex, this.props.subMenuKey); }, render() { diff --git a/src/MenuMixin.js b/src/MenuMixin.js index ef418d2b..a960afc4 100644 --- a/src/MenuMixin.js +++ b/src/MenuMixin.js @@ -92,16 +92,11 @@ const MenuMixin = { componentWillReceiveProps(nextProps) { let activeKey; - if ('activeKey' in nextProps) { - activeKey = getActiveKey(nextProps, nextProps.activeKey); + const originalActiveKey = this.getStore().getState().activeKey[this.getEventKey()]; + activeKey = getActiveKey(nextProps, originalActiveKey); + // fix: this.setState(), parent.render(), + if (activeKey !== originalActiveKey) { updateActiveKey(this.getStore(), this.getEventKey(), activeKey); - } else { - const originalActiveKey = this.getStore().getState().activeKey[this.getEventKey()]; - activeKey = getActiveKey(nextProps, originalActiveKey); - // fix: this.setState(), parent.render(), - if (activeKey !== originalActiveKey) { - updateActiveKey(this.getStore(), this.getEventKey(), activeKey); - } } }, @@ -256,7 +251,10 @@ const MenuMixin = { visible={props.visible} {...domProps} > - {React.Children.map(props.children, this.renderMenuItem)} + {React.Children.map( + props.children, + (c, i, subIndex) => this.renderMenuItem(c, i, subIndex, props.eventKey || '0-menu-'), + )} /*eslint-enable */ ); diff --git a/src/SubMenu.jsx b/src/SubMenu.jsx index 2bb3d186..a2b34009 100644 --- a/src/SubMenu.jsx +++ b/src/SubMenu.jsx @@ -11,7 +11,6 @@ import placements from './placements'; import { noop, loopMenuItemRecusively, - getMenuIdFromItemEventKey, getMenuIdFromSubMenuEventKey, } from './util'; @@ -452,7 +451,7 @@ const SubMenu = createReactClass({ SubMenu.isSubMenu = 1; -export default connect(({ openKeys, activeKey }, { eventKey }) => ({ +export default connect(({ openKeys, activeKey }, { eventKey, subMenuKey }) => ({ isOpen: openKeys.indexOf(eventKey) > -1, - active: activeKey[getMenuIdFromItemEventKey(eventKey)] === eventKey, + active: activeKey[subMenuKey] === eventKey, }))(SubMenu); diff --git a/src/SubPopupMenu.js b/src/SubPopupMenu.js index f05ab08b..76655b0e 100644 --- a/src/SubPopupMenu.js +++ b/src/SubPopupMenu.js @@ -66,7 +66,7 @@ const SubPopupMenu = createReactClass({ return this.props.openTransitionName; }, - renderMenuItem(c, i, subIndex) { + renderMenuItem(c, i, subIndex, subMenuKey) { if (!c) { return null; } @@ -75,6 +75,7 @@ const SubPopupMenu = createReactClass({ openKeys: props.openKeys, selectedKeys: props.selectedKeys, triggerSubMenuAction: props.triggerSubMenuAction, + subMenuKey, }; return this.renderCommonMenuItem(c, i, subIndex, extraProps); }, diff --git a/src/util.js b/src/util.js index f1bbda11..fbb8739e 100644 --- a/src/util.js +++ b/src/util.js @@ -8,23 +8,6 @@ export function getKeyFromChildrenIndex(child, menuEventKey, index) { return child.key || `${prefix}item_${index}`; } -/* - find which menu an item with particular event that it belongs to. - e.g.: - eventkey of '1' or '2' belongs to root menu ('0-menu-') - eventkey of '2-1' or '2-2' belongs to sub menu with key id '2-menu-' - eventkey of '2-2-1' or '2-2-2' belongs to sub menu with key id '2-2-menu-' -*/ -export function getMenuIdFromItemEventKey(eventkey) { - const index = eventkey.lastIndexOf('-'); - if (index === -1) { - return '0-menu-'; - } - - const ret = eventkey.slice(0, index); - return `${ret}-menu-`; -} - export function getMenuIdFromSubMenuEventKey(eventKey) { return `${eventKey}-menu-`; }