diff --git a/docs/examples/simple.tsx b/docs/examples/simple.tsx
index 37131216..fbfefea2 100644
--- a/docs/examples/simple.tsx
+++ b/docs/examples/simple.tsx
@@ -1,6 +1,6 @@
/* eslint no-console:0 */
-import Trigger, { ActionType } from '@rc-component/trigger';
+import Trigger, { type ActionType } from '@rc-component/trigger';
import React from 'react';
import '../../assets/index.less';
@@ -43,29 +43,26 @@ function getPopupContainer(trigger) {
return trigger.parentNode;
}
-const InnerTarget = (props) => (
-
-
This is a example of trigger usage.
-
You can adjust the value above
-
which will also change the behaviour of popup.
-
+const InnerTarget = React.forwardRef(
+ (props: any, ref: React.Ref) => (
+
+
This is a example of trigger usage.
+
You can adjust the value above
+
which will also change the behavior of popup.
+
+ ),
);
-const RefTarget = React.forwardRef((props, ref) => {
- React.useImperativeHandle(ref, () => ({}));
-
- return ;
-});
-
interface TestState {
mask: boolean;
maskClosable: boolean;
@@ -88,7 +85,7 @@ interface TestState {
class Test extends React.Component {
state: TestState = {
- mask: true,
+ mask: false,
maskClosable: true,
placement: 'bottom',
trigger: {
@@ -377,7 +374,7 @@ class Test extends React.Component {
motionName: state.transitionName,
}}
>
-
+
diff --git a/src/index.tsx b/src/index.tsx
index 83923b72..861a1b37 100644
--- a/src/index.tsx
+++ b/src/index.tsx
@@ -595,6 +595,10 @@ export function generateTrigger(
};
}
+ // ============================ Perf ============================
+ const rendedRef = React.useRef(false);
+ rendedRef.current ||= forceRender || mergedOpen || inMotion;
+
// =========================== Render ===========================
const mergedChildrenProps = {
...originChildProps,
@@ -651,55 +655,57 @@ export function generateTrigger(
>
{triggerNode}
-
-
-
+ {rendedRef.current && (
+
+
+
+ )}
>
);
});
diff --git a/tests/perf.test.tsx b/tests/perf.test.tsx
new file mode 100644
index 00000000..a02d0861
--- /dev/null
+++ b/tests/perf.test.tsx
@@ -0,0 +1,102 @@
+import { cleanup, fireEvent, render } from '@testing-library/react';
+import { spyElementPrototypes } from '@rc-component/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);
+ });
+ });
+});