Skip to content

Commit

Permalink
perf: menu lazy render children #4812
Browse files Browse the repository at this point in the history
  • Loading branch information
tangjinzhou committed Dec 9, 2021
1 parent 30f87e6 commit 95a1b4a
Show file tree
Hide file tree
Showing 17 changed files with 432 additions and 219 deletions.
4 changes: 2 additions & 2 deletions components/dropdown/demo/sub-menu.vue
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,11 @@ The menu has multiple levels.
<a-menu>
<a-menu-item>1st menu item</a-menu-item>
<a-menu-item>2nd menu item</a-menu-item>
<a-sub-menu key="test" title="sub menu">
<a-sub-menu key="sub1" title="sub menu">
<a-menu-item>3rd menu item</a-menu-item>
<a-menu-item>4th menu item</a-menu-item>
</a-sub-menu>
<a-sub-menu title="disabled sub menu" disabled>
<a-sub-menu key="sub2" title="disabled sub menu" disabled>
<a-menu-item>5d menu item</a-menu-item>
<a-menu-item>6th menu item</a-menu-item>
</a-sub-menu>
Expand Down
155 changes: 113 additions & 42 deletions components/layout/__tests__/__snapshots__/demo.test.js.snap

Large diffs are not rendered by default.

244 changes: 156 additions & 88 deletions components/menu/__tests__/__snapshots__/demo.test.js.snap

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion components/menu/__tests__/index.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ describe('Menu', () => {
{ attachTo: 'body', sync: false },
);
await asyncExpect(() => {
expect($$('.ant-menu-submenu-selected').length).toBe(2);
expect($$('li.ant-menu-submenu-selected').length).toBe(1);
});
});
it('should accept openKeys in mode horizontal', async () => {
Expand Down
2 changes: 1 addition & 1 deletion components/menu/demo/horizontal.vue
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ Horizontal top navigation menu.
</template>
Navigation Two
</a-menu-item>
<a-sub-menu>
<a-sub-menu key="sub1">
<template #icon>
<setting-outlined />
</template>
Expand Down
2 changes: 1 addition & 1 deletion components/menu/demo/inline-collapsed.vue
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ export default defineComponent({
watch(
() => state.openKeys,
(val, oldVal) => {
(_val, oldVal) => {
state.preOpenKeys = oldVal;
},
);
Expand Down
6 changes: 4 additions & 2 deletions components/menu/demo/template.vue
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ Use the single file method to recursively generate menus.
<MenuFoldOutlined v-else />
</a-button>
<a-menu
:default-selected-keys="['1']"
:default-open-keys="['2']"
v-model:openKeys="openKeys"
v-model:selectedKeys="selectedKeys"
mode="inline"
theme="dark"
:inline-collapsed="collapsed"
Expand Down Expand Up @@ -119,6 +119,8 @@ export default defineComponent({
list,
collapsed,
toggleCollapsed,
selectedKeys: ref(['1']),
openKeys: ref(['2']),
};
},
});
Expand Down
2 changes: 1 addition & 1 deletion components/menu/index.en-US.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ More layouts with navigation: [Layout](/components/layout).
<template>
<a-menu>
<a-menu-item>Menu</a-menu-item>
<a-sub-menu title="SubMenu">
<a-sub-menu key="sub1" title="SubMenu">
<a-menu-item>SubMenuItem</a-menu-item>
</a-sub-menu>
</a-menu>
Expand Down
2 changes: 1 addition & 1 deletion components/menu/index.zh-CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ cover: https://gw.alipayobjects.com/zos/alicdn/3XZcjGpvK/Menu.svg
<template>
<a-menu>
<a-menu-item>菜单项</a-menu-item>
<a-sub-menu title="子菜单">
<a-sub-menu key="sub1" title="子菜单">
<a-menu-item>子菜单项</a-menu-item>
</a-sub-menu>
</a-menu>
Expand Down
3 changes: 3 additions & 0 deletions components/menu/src/ItemGroup.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import type { ExtractPropTypes } from 'vue';
import { computed, defineComponent } from 'vue';
import PropTypes from '../../_util/vue-types';
import { useInjectMenu } from './hooks/useMenuContext';
import { useMeasure } from './hooks/useKeyPath';

const menuItemGroupProps = {
title: PropTypes.VNodeChild,
Expand All @@ -18,7 +19,9 @@ export default defineComponent({
setup(props, { slots, attrs }) {
const { prefixCls } = useInjectMenu();
const groupPrefixCls = computed(() => `${prefixCls.value}-item-group`);
const isMeasure = useMeasure();
return () => {
if (isMeasure) return slots.default?.();
return (
<li {...attrs} onClick={e => e.stopPropagation()} class={groupPrefixCls.value}>
<div
Expand Down
131 changes: 76 additions & 55 deletions components/menu/src/Menu.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Key } from '../../_util/type';
import type { ExtractPropTypes, PropType, UnwrapRef } from 'vue';
import type { ExtractPropTypes, PropType } from 'vue';
import { computed, defineComponent, ref, inject, watchEffect, watch, onMounted, unref } from 'vue';
import shallowEqual from '../../_util/shallowequal';
import type { StoreMenuInfo } from './hooks/useMenuContext';
Expand All @@ -24,13 +24,15 @@ import MenuItem from './MenuItem';
import SubMenu from './SubMenu';
import EllipsisOutlined from '@ant-design/icons-vue/EllipsisOutlined';
import { cloneElement } from '../../_util/vnode';
import { OVERFLOW_KEY, PathContext } from './hooks/useKeyPath';

export const menuProps = {
id: String,
prefixCls: String,
disabled: Boolean,
inlineCollapsed: Boolean,
disabledOverflow: Boolean,
forceSubMenuRender: Boolean,
openKeys: Array,
selectedKeys: Array,
activeKey: String, // 内部组件使用
Expand Down Expand Up @@ -60,6 +62,7 @@ export type MenuProps = Partial<ExtractPropTypes<typeof menuProps>>;
const EMPTY_LIST: string[] = [];
export default defineComponent({
name: 'AMenu',
inheritAttrs: false,
props: menuProps,
emits: [
'update:openKeys',
Expand All @@ -71,7 +74,7 @@ export default defineComponent({
'update:activeKey',
],
slots: ['expandIcon', 'overflowedIndicator'],
setup(props, { slots, emit }) {
setup(props, { slots, emit, attrs }) {
const { prefixCls, direction } = useConfigInject('menu', props);
const store = ref<Record<string, StoreMenuInfo>>({});
const siderCollapsed = inject(SiderCollapsedKey, ref(undefined));
Expand Down Expand Up @@ -102,7 +105,7 @@ export default defineComponent({

const activeKeys = ref([]);
const mergedSelectedKeys = ref([]);
const keyMapStore = ref({});
const keyMapStore = ref<Record<Key, StoreMenuInfo>>({});
watch(
store,
() => {
Expand All @@ -117,11 +120,9 @@ export default defineComponent({
watchEffect(() => {
if (props.activeKey !== undefined) {
let keys = [];
const menuInfo = props.activeKey
? (keyMapStore.value[props.activeKey] as UnwrapRef<StoreMenuInfo>)
: undefined;
const menuInfo = props.activeKey ? keyMapStore.value[props.activeKey] : undefined;
if (menuInfo && props.activeKey !== undefined) {
keys = [...menuInfo.parentKeys, props.activeKey];
keys = uniq([].concat(unref(menuInfo.parentKeys), props.activeKey));
} else {
keys = [];
}
Expand All @@ -139,22 +140,21 @@ export default defineComponent({
{ immediate: true },
);

const selectedSubMenuEventKeys = ref([]);

const selectedSubMenuKeys = ref([]);
watch(
[keyMapStore, mergedSelectedKeys],
() => {
let subMenuParentEventKeys = [];
let subMenuParentKeys = [];
mergedSelectedKeys.value.forEach(key => {
const menuInfo = keyMapStore.value[key];
if (menuInfo) {
subMenuParentEventKeys.push(...unref(menuInfo.parentEventKeys));
subMenuParentKeys = subMenuParentKeys.concat(unref(menuInfo.parentKeys));
}
});

subMenuParentEventKeys = uniq(subMenuParentEventKeys);
if (!shallowEqual(selectedSubMenuEventKeys.value, subMenuParentEventKeys)) {
selectedSubMenuEventKeys.value = subMenuParentEventKeys;
subMenuParentKeys = uniq(subMenuParentKeys);
if (!shallowEqual(selectedSubMenuKeys.value, subMenuParentKeys)) {
selectedSubMenuKeys.value = subMenuParentKeys;
}
},
{ immediate: true },
Expand Down Expand Up @@ -321,16 +321,16 @@ export default defineComponent({
triggerSelection(info);
};

const onInternalOpenChange = (eventKey: Key, open: boolean) => {
const { key, childrenEventKeys } = store.value[eventKey];
const onInternalOpenChange = (key: Key, open: boolean) => {
const childrenEventKeys = keyMapStore.value[key].childrenEventKeys;
let newOpenKeys = mergedOpenKeys.value.filter(k => k !== key);

if (open) {
newOpenKeys.push(key);
} else if (mergedMode.value !== 'inline') {
// We need find all related popup to close
const subPathKeys = getChildrenKeys(childrenEventKeys);
newOpenKeys = newOpenKeys.filter(k => !subPathKeys.includes(k));
newOpenKeys = uniq(newOpenKeys.filter(k => !subPathKeys.includes(k)));
}

if (!shallowEqual(mergedOpenKeys, newOpenKeys)) {
Expand Down Expand Up @@ -388,9 +388,10 @@ export default defineComponent({
onItemClick: onInternalClick,
registerMenuInfo,
unRegisterMenuInfo,
selectedSubMenuEventKeys,
selectedSubMenuKeys,
isRootMenu: ref(true),
expandIcon,
forceSubMenuRender: computed(() => props.forceSubMenuRender),
});
return () => {
const childList = flattenChildren(slots.default?.());
Expand All @@ -415,43 +416,63 @@ export default defineComponent({
const overflowedIndicator = slots.overflowedIndicator?.() || <EllipsisOutlined />;

return (
<Overflow
prefixCls={`${prefixCls.value}-overflow`}
component="ul"
itemComponent={MenuItem}
class={className.value}
role="menu"
id={props.id}
data={wrappedChildList}
renderRawItem={node => node}
renderRawRest={omitItems => {
// We use origin list since wrapped list use context to prevent open
const len = omitItems.length;

const originOmitItems = len ? childList.slice(-len) : null;

return (
<SubMenu
eventKey={Overflow.OVERFLOW_KEY}
title={overflowedIndicator}
disabled={allVisible}
internalPopupClose={len === 0}
>
{originOmitItems}
</SubMenu>
);
}}
maxCount={
mergedMode.value !== 'horizontal' || props.disabledOverflow
? Overflow.INVALIDATE
: Overflow.RESPONSIVE
}
ssr="full"
data-menu-list
onVisibleChange={newLastIndex => {
lastVisibleIndex.value = newLastIndex;
}}
/>
<>
<Overflow
{...attrs}
prefixCls={`${prefixCls.value}-overflow`}
component="ul"
itemComponent={MenuItem}
class={className.value}
role="menu"
id={props.id}
data={wrappedChildList}
renderRawItem={node => node}
renderRawRest={omitItems => {
// We use origin list since wrapped list use context to prevent open
const len = omitItems.length;

const originOmitItems = len ? childList.slice(-len) : null;

return (
<>
<SubMenu
eventKey={OVERFLOW_KEY}
key={OVERFLOW_KEY}
title={overflowedIndicator}
disabled={allVisible}
internalPopupClose={len === 0}
>
{originOmitItems}
</SubMenu>
<PathContext>
<SubMenu
eventKey={OVERFLOW_KEY}
key={OVERFLOW_KEY}
title={overflowedIndicator}
disabled={allVisible}
internalPopupClose={len === 0}
>
{originOmitItems}
</SubMenu>
</PathContext>
</>
);
}}
maxCount={
mergedMode.value !== 'horizontal' || props.disabledOverflow
? Overflow.INVALIDATE
: Overflow.RESPONSIVE
}
ssr="full"
data-menu-list
onVisibleChange={newLastIndex => {
lastVisibleIndex.value = newLastIndex;
}}
/>
<div style={{ display: 'none' }} aria-hidden>
<PathContext>{wrappedChildList}</PathContext>
</div>
</>
);
};
},
Expand Down
6 changes: 3 additions & 3 deletions components/menu/src/MenuItem.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { flattenChildren, getPropsSlot, isValidElement } from '../../_util/props
import PropTypes from '../../_util/vue-types';
import type { ExtractPropTypes } from 'vue';
import { computed, defineComponent, getCurrentInstance, onBeforeUnmount, ref, watch } from 'vue';
import { useInjectKeyPath } from './hooks/useKeyPath';
import { useInjectKeyPath, useMeasure } from './hooks/useKeyPath';
import { useInjectFirstLevel, useInjectMenu } from './hooks/useMenuContext';
import { cloneElement } from '../../_util/vnode';
import Tooltip from '../../tooltip';
Expand Down Expand Up @@ -32,7 +32,7 @@ export default defineComponent({
slots: ['icon', 'title'],
setup(props, { slots, emit, attrs }) {
const instance = getCurrentInstance();

const isMeasure = useMeasure();
const key =
typeof instance.vnode.key === 'symbol' ? String(instance.vnode.key) : instance.vnode.key;
devWarning(
Expand Down Expand Up @@ -70,7 +70,6 @@ export default defineComponent({
parentKeys,
isLeaf: true,
};

registerMenuInfo(eventKey, menuInfo);

onBeforeUnmount(() => {
Expand Down Expand Up @@ -174,6 +173,7 @@ export default defineComponent({
const directionStyle = useDirectionStyle(computed(() => keysPath.value.length));

return () => {
if (isMeasure) return null;
const title = props.title ?? slots.title?.();
const children = flattenChildren(slots.default?.());
const childrenLength = children.length;
Expand Down
7 changes: 4 additions & 3 deletions components/menu/src/PopupTrigger.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import Trigger from '../../vc-trigger';
import type { PropType } from 'vue';
import { computed, defineComponent, onBeforeUnmount, ref, watch } from 'vue';
import type { MenuMode } from './interface';
import { useInjectMenu } from './hooks/useMenuContext';
import { useInjectForceRender, useInjectMenu } from './hooks/useMenuContext';
import { placements, placementsRtl } from './placements';
import type { RafFrame } from '../../_util/raf';
import raf from '../../_util/raf';
Expand Down Expand Up @@ -39,8 +39,9 @@ export default defineComponent({
builtinPlacements,
triggerSubMenuAction,
isRootMenu,
forceSubMenuRender,
} = useInjectMenu();

const forceRender = useInjectForceRender();
const placement = computed(() =>
rtl.value
? { ...placementsRtl, ...builtinPlacements.value }
Expand Down Expand Up @@ -91,7 +92,7 @@ export default defineComponent({
mouseEnterDelay={subMenuOpenDelay.value}
mouseLeaveDelay={subMenuCloseDelay.value}
onPopupVisibleChange={onVisibleChange}
forceRender={true}
forceRender={forceRender || forceSubMenuRender.value}
v-slots={{
popup: () => {
return slots.popup?.({ visible: innerVisible.value });
Expand Down
Loading

0 comments on commit 95a1b4a

Please sign in to comment.