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);
+ });
+ });
+});