From 01c0321b1782ee3fd7d668fdc77e8af268199d51 Mon Sep 17 00:00:00 2001 From: lijianan <574980606@qq.com> Date: Mon, 13 Oct 2025 23:14:52 +0800 Subject: [PATCH] chore: remove unstable api for React 19 compitable (#55274) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * chore: remove unstable api for React 19 compitable * update * update * update * update antd-token-previewer * update * update --------- Co-authored-by: 遇见同学 <1875694521@qq.com> --- .dumi/theme/builtins/Previewer/Actions.tsx | 1 - .../builtins/Previewer/stackblitzConfig.ts | 1 - .dumi/theme/layouts/GlobalLayout.tsx | 2 - .../__snapshots__/index.test.ts.snap | 1 - components/__tests__/unstable.test.ts | 49 ----------------- components/_util/wave/WaveEffect.tsx | 28 ++-------- .../config-provider/UnstableContext.tsx | 29 ---------- components/index.ts | 4 -- components/message/index.tsx | 7 +-- components/modal/confirm.tsx | 23 ++++---- components/notification/index.tsx | 7 +-- docs/react/v5-for-19.en-US.md | 54 ------------------- docs/react/v5-for-19.zh-CN.md | 54 ------------------- package.json | 3 +- tests/__snapshots__/index.test.ts.snap | 1 - 15 files changed, 19 insertions(+), 245 deletions(-) delete mode 100644 components/__tests__/unstable.test.ts delete mode 100644 components/config-provider/UnstableContext.tsx delete mode 100644 docs/react/v5-for-19.en-US.md delete mode 100644 docs/react/v5-for-19.zh-CN.md diff --git a/.dumi/theme/builtins/Previewer/Actions.tsx b/.dumi/theme/builtins/Previewer/Actions.tsx index ea67f72c99..61b0282174 100644 --- a/.dumi/theme/builtins/Previewer/Actions.tsx +++ b/.dumi/theme/builtins/Previewer/Actions.tsx @@ -211,7 +211,6 @@ createRoot(document.getElementById('container')).render(); 'react-dom': '^19.0.0', '@types/react': '^19.0.0', '@types/react-dom': '^19.0.0', - '@ant-design/v5-patch-for-react-19': '^1.0.3', }, demoJsContent, indexCssContent, diff --git a/.dumi/theme/builtins/Previewer/stackblitzConfig.ts b/.dumi/theme/builtins/Previewer/stackblitzConfig.ts index 3d147caf6e..f34715694b 100644 --- a/.dumi/theme/builtins/Previewer/stackblitzConfig.ts +++ b/.dumi/theme/builtins/Previewer/stackblitzConfig.ts @@ -122,7 +122,6 @@ const getStackblitzConfig = ({ // main.tsx [`src/main.${_suffix}`]: `import { StrictMode } from 'react'; import { createRoot } from 'react-dom/client'; -import '@ant-design/v5-patch-for-react-19'; import Demo from './demo'; createRoot(document.getElementById('container')${suffix === 'tsx' ? '!' : ''}).render( diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx index b6f602283e..38d46b9c18 100644 --- a/.dumi/theme/layouts/GlobalLayout.tsx +++ b/.dumi/theme/layouts/GlobalLayout.tsx @@ -22,8 +22,6 @@ import type { SimpleComponentClassNames, SiteContextProps } from '../slots/SiteC import SiteContext from '../slots/SiteContext'; import { isLocalStorageNameSupported } from '../utils'; -import '@ant-design/v5-patch-for-react-19'; - type SiteState = Partial>; const RESPONSIVE_MOBILE = 768; diff --git a/components/__tests__/__snapshots__/index.test.ts.snap b/components/__tests__/__snapshots__/index.test.ts.snap index 75e5d985b7..24753185e6 100644 --- a/components/__tests__/__snapshots__/index.test.ts.snap +++ b/components/__tests__/__snapshots__/index.test.ts.snap @@ -74,7 +74,6 @@ exports[`antd exports modules correctly 1`] = ` "message", "notification", "theme", - "unstableSetRender", "version", ] `; diff --git a/components/__tests__/unstable.test.ts b/components/__tests__/unstable.test.ts deleted file mode 100644 index fa9d431bff..0000000000 --- a/components/__tests__/unstable.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import * as ReactDOM from 'react-dom'; -import { Modal, unstableSetRender } from 'antd'; - -import { waitFakeTimer19 } from '../../tests/utils'; - -// TODO: Remove this. Mock for React 19 -jest.mock('react-dom', () => { - const realReactDOM = jest.requireActual('react-dom'); - - if (realReactDOM.version.startsWith('19')) { - const realReactDOMClient = jest.requireActual('react-dom/client'); - realReactDOM.createRoot = realReactDOMClient.createRoot; - } - - return realReactDOM; -}); - -describe('unstable', () => { - beforeEach(() => { - jest.useFakeTimers(); - }); - - afterEach(() => { - jest.useRealTimers(); - }); - - it('unstableSetRender', async () => { - if (ReactDOM.version.startsWith('19')) { - unstableSetRender((node, container) => { - const root = (ReactDOM as any).createRoot(container); - root.render(node); - return async () => { - root.unmount(); - }; - }); - - Modal.info({ content: 'unstableSetRender' }); - - await waitFakeTimer19(); - - expect(document.querySelector('.ant-modal')).toBeTruthy(); - } - }); - - it('unstableSetRender without param', async () => { - const currentRender = unstableSetRender(); - expect(currentRender).toBeInstanceOf(Function); - }); -}); diff --git a/components/_util/wave/WaveEffect.tsx b/components/_util/wave/WaveEffect.tsx index e7878bdb00..7ada0ff2c8 100644 --- a/components/_util/wave/WaveEffect.tsx +++ b/components/_util/wave/WaveEffect.tsx @@ -1,12 +1,11 @@ import * as React from 'react'; import CSSMotion from '@rc-component/motion'; import raf from '@rc-component/util/lib/raf'; +import { render, unmount } from '@rc-component/util/lib/React/render'; import { composeRef } from '@rc-component/util/lib/ref'; import { clsx } from 'clsx'; import type { WaveProps } from '.'; -import { unstableSetRender } from '../../config-provider/UnstableContext'; -import type { UnmountType } from '../../config-provider/UnstableContext'; import { TARGET_CLS } from './interface'; import type { ShowWaveEffect } from './interface'; import { getTargetWaveColor } from './util'; @@ -19,21 +18,13 @@ export interface WaveEffectProps { className: string; target: HTMLElement; component?: string; - registerUnmount: () => UnmountType | null; colorSource?: WaveProps['colorSource']; } const WaveEffect = (props: WaveEffectProps) => { - const { className, target, component, registerUnmount, colorSource } = props; + const { className, target, component, colorSource } = props; const divRef = React.useRef(null); - // ====================== Refs ====================== - const unmountRef = React.useRef(null); - - React.useEffect(() => { - unmountRef.current = registerUnmount(); - }, []); - // ===================== Effect ===================== const [color, setWaveColor] = React.useState(null); const [borderRadius, setBorderRadius] = React.useState([]); @@ -129,7 +120,7 @@ const WaveEffect = (props: WaveEffectProps) => { onAppearEnd={(_, event) => { if (event.deadline || (event as TransitionEvent).propertyName === 'opacity') { const holder = divRef.current?.parentElement!; - unmountRef.current?.().then(() => { + unmount(holder).then(() => { holder?.remove(); }); } @@ -162,18 +153,7 @@ const showWaveEffect: ShowWaveEffect = (target, info) => { holder.style.top = '0px'; target?.insertBefore(holder, target?.firstChild); - const reactRender = unstableSetRender(); - - let unmountCallback: UnmountType | null = null; - - function registerUnmount() { - return unmountCallback; - } - - unmountCallback = reactRender( - , - holder, - ); + render(, holder); }; export default showWaveEffect; diff --git a/components/config-provider/UnstableContext.tsx b/components/config-provider/UnstableContext.tsx deleted file mode 100644 index 982c58318b..0000000000 --- a/components/config-provider/UnstableContext.tsx +++ /dev/null @@ -1,29 +0,0 @@ -import type * as React from 'react'; -import { render, unmount } from '@rc-component/util/lib/React/render'; - -export type UnmountType = () => Promise; -export type RenderType = ( - node: React.ReactElement, - container: Element | DocumentFragment, -) => UnmountType; - -const defaultReactRender: RenderType = (node, container) => { - render(node, container); - return () => { - return unmount(container); - }; -}; - -let unstableRender: RenderType = defaultReactRender; - -/** - * @deprecated Set React render function for compatible usage. - * This is internal usage only compatible with React 19. - * And will be removed in next major version. - */ -export function unstableSetRender(render?: RenderType) { - if (render) { - unstableRender = render; - } - return unstableRender; -} diff --git a/components/index.ts b/components/index.ts index 3a642e557b..0e730c9af1 100644 --- a/components/index.ts +++ b/components/index.ts @@ -175,7 +175,3 @@ export type { DraggerProps, UploadFile, UploadProps } from './upload'; export { default as version } from './version'; export { default as Watermark } from './watermark'; export type { WatermarkProps } from './watermark'; - -// TODO: Remove in v6 -/* eslint-disable-next-line perfectionist/sort-exports */ -export { unstableSetRender } from './config-provider/UnstableContext'; diff --git a/components/message/index.tsx b/components/message/index.tsx index cc95756f4e..b24b3b1124 100755 --- a/components/message/index.tsx +++ b/components/message/index.tsx @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; +import { render } from '@rc-component/util/lib/React/render'; import { AppConfigContext } from '../app/context'; import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider'; -import { unstableSetRender } from '../config-provider/UnstableContext'; import type { ArgsProps, ConfigOptions, @@ -132,13 +132,10 @@ const flushMessageQueue = () => { // Delay render to avoid sync issue act(() => { - const reactRender = unstableSetRender(); - - reactRender( + render( { const { instance, sync } = node || {}; - // React 18 test env will throw if call immediately in ref Promise.resolve().then(() => { if (!newMessage.instance && instance) { diff --git a/components/modal/confirm.tsx b/components/modal/confirm.tsx index ccac1d8ebd..34960cd54f 100644 --- a/components/modal/confirm.tsx +++ b/components/modal/confirm.tsx @@ -1,9 +1,8 @@ import React, { useContext } from 'react'; +import { render, unmount } from '@rc-component/util/lib/React/render'; import warning from '../_util/warning'; import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider'; -import { unstableSetRender } from '../config-provider/UnstableContext'; -import type { UnmountType } from '../config-provider/UnstableContext'; import type { ConfirmDialogProps } from './ConfirmDialog'; import ConfirmDialog from './ConfirmDialog'; import destroyFns from './destroyFns'; @@ -72,8 +71,6 @@ export default function confirm(config: ModalFuncProps) { let currentConfig = { ...config, close, open: true } as any; let timeoutId: ReturnType; - let reactUnmount: UnmountType; - function destroy(...args: any[]) { const triggerCancel = args.some((param) => param?.triggerCancel); if (triggerCancel) { @@ -87,10 +84,12 @@ export default function confirm(config: ModalFuncProps) { } } - reactUnmount(); + unmount(container).then(() => { + // Do nothing + }); } - function render(props: any) { + function scheduleRender(props: any) { clearTimeout(timeoutId); /** @@ -105,11 +104,9 @@ export default function confirm(config: ModalFuncProps) { const dom = ; - const reactRender = unstableSetRender(); - - reactUnmount = reactRender( + render( - {global.holderRender ? global.holderRender(dom) : dom} + {typeof global.holderRender === 'function' ? global.holderRender(dom) : dom} , container, ); @@ -129,7 +126,7 @@ export default function confirm(config: ModalFuncProps) { }, }; - render(currentConfig); + scheduleRender(currentConfig); } function update(configUpdate: ConfigUpdate) { @@ -141,10 +138,10 @@ export default function confirm(config: ModalFuncProps) { ...configUpdate, }; } - render(currentConfig); + scheduleRender(currentConfig); } - render(currentConfig); + scheduleRender(currentConfig); destroyFns.push(close); diff --git a/components/notification/index.tsx b/components/notification/index.tsx index 9e0cd433c6..93c5252cb2 100755 --- a/components/notification/index.tsx +++ b/components/notification/index.tsx @@ -1,8 +1,8 @@ import React, { useContext } from 'react'; +import { render } from '@rc-component/util/lib/React/render'; import { AppConfigContext } from '../app/context'; import ConfigProvider, { ConfigContext, globalConfig, warnContext } from '../config-provider'; -import { unstableSetRender } from '../config-provider/UnstableContext'; import type { ArgsProps, GlobalConfigProps, NotificationInstance } from './interface'; import PurePanel from './PurePanel'; import useNotification, { useInternalNotification } from './useNotification'; @@ -126,13 +126,10 @@ const flushNotificationQueue = () => { // Delay render to avoid sync issue act(() => { - const reactRender = unstableSetRender(); - - reactRender( + render( { const { instance, sync } = node || {}; - Promise.resolve().then(() => { if (!newNotification.instance && instance) { newNotification.instance = instance; diff --git a/docs/react/v5-for-19.en-US.md b/docs/react/v5-for-19.en-US.md deleted file mode 100644 index c874b1d1fc..0000000000 --- a/docs/react/v5-for-19.en-US.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -order: 1 -title: React 19 Compatibility -tag: New ---- - - -:::info{title="Compatibility Interface"} -antd v5 is compatible with React 16 ~ 18 by default, and most features are also compatible with React 19. A few issues are listed below, and the following compatibility methods can be used for adaptation. This method and interface will be removed in v6. -:::: - -### React 19 Compatibility Issues - -Due to React 19 adjusting the export method of `react-dom`, antd cannot directly use the `ReactDOM.render` method. Therefore, using antd will encounter the following issues: - -- Wave effect does not work properly -- `Modal`、`Notification`、`Message` and other components' static methods are invalid (hooks invocation methods are not affected). - -Therefore, you need to use a compatibility configuration to make antd work properly in React 19. - -### Compatibility Methods - -You can choose one of the following methods, and it is recommended to use the compatibility package first. - -#### Compatibility Package - -Install the compatibility package - - - -Import the compatibility package at the application entry - -```tsx -import '@ant-design/v5-patch-for-react-19'; -``` - -#### unstableSetRender - -Once again, please use the compatibility package first. Only for special scenarios such as umd, micro-applications, etc., use the `unstableSetRender` method. `unstableSetRender` is a low-level registration method that allows developers to modify the rendering method of ReactDOM. Write the following code at the entry of your application: - -```js -import { unstableSetRender } from 'antd'; -import { createRoot } from 'react-dom/client'; - -unstableSetRender((node, container) => { - container._reactRoot ||= createRoot(container); - const root = container._reactRoot; - root.render(node); - return async () => { - await new Promise((resolve) => setTimeout(resolve, 0)); - root.unmount(); - }; -}); -``` diff --git a/docs/react/v5-for-19.zh-CN.md b/docs/react/v5-for-19.zh-CN.md deleted file mode 100644 index 7615cbb4e2..0000000000 --- a/docs/react/v5-for-19.zh-CN.md +++ /dev/null @@ -1,54 +0,0 @@ ---- -order: 1 -title: React 19 兼容 -tag: New ---- - - -:::info{title="兼容接口"} -antd v5 默认兼容 React 16 ~ 18 版本,绝大多数功能也兼容于 React 19 版本。少数问题如下所示,可使用以下兼容方法进行适配。该方式以及接口将在 v6 被移除。 -::: - -### React 19 兼容问题 - -由于 React 19 调整了 `react-dom` 的导出方式,导致 antd 无法直接使用 `ReactDOM.render` 方法。因而使用 antd 会遇到以下问题: - -- 波纹特效无法正常工作 -- `Modal`、`Notification`、`Message` 等组件的静态方法无效(hooks 调用方式不受影响) - -因而需要通过兼容配置,使 antd 在 React 19 中正常工作。 - -### 兼容方式 - -以下方法任选其一,其中优先推荐使用兼容包。 - -#### 兼容包 - -安装兼容包 - - - -在应用入口处引入兼容包 - -```tsx -import '@ant-design/v5-patch-for-react-19'; -``` - -#### unstableSetRender - -再次提醒,默认情况下,请优先使用兼容包。除非对于 umd、微应用等特殊场景,才使用 `unstableSetRender` 方法。`unstableSetRender` 为底层注册方法,允许开发者修改 ReactDOM 的渲染方法。在你的应用入口处写入: - -```js -import { unstableSetRender } from 'antd'; -import { createRoot } from 'react-dom/client'; - -unstableSetRender((node, container) => { - container._reactRoot ||= createRoot(container); - const root = container._reactRoot; - root.render(node); - return async () => { - await new Promise((resolve) => setTimeout(resolve, 0)); - root.unmount(); - }; -}); -``` diff --git a/package.json b/package.json index 0e942ef194..cd472e07c0 100644 --- a/package.json +++ b/package.json @@ -161,7 +161,6 @@ "@ant-design/compatible": "^5.1.4", "@ant-design/happy-work-theme": "^1.0.1", "@ant-design/tools": "^19.0.3", - "@ant-design/v5-patch-for-react-19": "^1.0.3", "@ant-design/x": "^1.6.1", "@antfu/eslint-config": "^5.4.1", "@antv/g6": "^4.8.25", @@ -225,7 +224,7 @@ "ali-oss": "^6.23.0", "antd-img-crop": "^4.27.0", "antd-style": "4.0.0-alpha.1", - "antd-token-previewer": "3.0.0-alpha.1", + "antd-token-previewer": "3.0.0-alpha.3", "axios": "^1.12.2", "chalk": "^5.6.2", "cheerio": "^1.1.2", diff --git a/tests/__snapshots__/index.test.ts.snap b/tests/__snapshots__/index.test.ts.snap index d9136a751c..ccf24ac5b5 100644 --- a/tests/__snapshots__/index.test.ts.snap +++ b/tests/__snapshots__/index.test.ts.snap @@ -74,7 +74,6 @@ exports[`antd dist files exports modules correctly 1`] = ` "message", "notification", "theme", - "unstableSetRender", "version", ] `;