diff --git a/src/index.tsx b/src/index.tsx index ac4b841e..db2988b8 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -644,6 +644,10 @@ export function generateTrigger( cloneProps.className = classNames(originChildProps.className, className); } + // ============================ Perf ============================ + const renderedRef = React.useRef(false); + renderedRef.current ||= forceRender || mergedOpen || inMotion; + // =========================== Render =========================== const mergedChildrenProps = { ...originChildProps, @@ -702,55 +706,57 @@ export function generateTrigger( {triggerNode} - - - + {renderedRef.current && ( + + + + )} ); }); diff --git a/tests/perf.test.tsx b/tests/perf.test.tsx new file mode 100644 index 00000000..e2d5cfc9 --- /dev/null +++ b/tests/perf.test.tsx @@ -0,0 +1,102 @@ +import { cleanup, fireEvent, render } from '@testing-library/react'; +import { spyElementPrototypes } from 'rc-util/lib/test/domHook'; +import React from 'react'; +import Trigger, { type TriggerProps } from '../src'; +import { awaitFakeTimer, placementAlignMap } from './util'; + +jest.mock('../src/Popup', () => { + const OriReact = jest.requireActual('react'); + const OriPopup = jest.requireActual('../src/Popup').default; + + return OriReact.forwardRef((props, ref) => { + global.popupCalledTimes = (global.popupCalledTimes || 0) + 1; + return ; + }); +}); + +describe('Trigger.Basic', () => { + beforeAll(() => { + spyElementPrototypes(HTMLElement, { + offsetParent: { + get: () => document.body, + }, + }); + }); + + beforeEach(() => { + global.popupCalledTimes = 0; + jest.useFakeTimers(); + }); + + afterEach(() => { + cleanup(); + jest.useRealTimers(); + }); + + async function trigger(dom: HTMLElement, selector: string, method = 'click') { + fireEvent[method](dom.querySelector(selector)); + await awaitFakeTimer(); + } + + const renderTrigger = (props?: Partial) => ( + tooltip2} + {...props} + > +
click
+
+ ); + + describe('Performance', () => { + it('not create Popup when !open', async () => { + const { container } = render(renderTrigger()); + + // Not render Popup + await awaitFakeTimer(); + expect(global.popupCalledTimes).toBe(0); + + // Now can render Popup + await trigger(container, '.target'); + expect(global.popupCalledTimes).toBeGreaterThan(0); + + expect(document.querySelector('.rc-trigger-popup')).toBeTruthy(); + }); + + it('forceRender should create when !open', async () => { + const { container } = render( + renderTrigger({ + forceRender: true, + }), + ); + + await awaitFakeTimer(); + await trigger(container, '.target'); + expect(global.popupCalledTimes).toBeGreaterThan(0); + + expect(document.querySelector('.rc-trigger-popup')).toBeTruthy(); + }); + + it('hide should keep render Popup', async () => { + const { rerender } = render( + renderTrigger({ + popupVisible: true, + }), + ); + + await awaitFakeTimer(); + expect(global.popupCalledTimes).toBeGreaterThan(0); + + // Hide + global.popupCalledTimes = 0; + rerender( + renderTrigger({ + popupVisible: false, + }), + ); + await awaitFakeTimer(); + expect(global.popupCalledTimes).toBeGreaterThan(0); + }); + }); +});