chore: remove unstable api for React 19 compitable (#55274)

* chore: remove unstable api for React 19 compitable

* update

* update

* update

* update antd-token-previewer

* update

* update

---------

Co-authored-by: 遇见同学 <1875694521@qq.com>
This commit is contained in:
lijianan
2025-10-13 23:14:52 +08:00
committed by GitHub
parent 4e8d79581e
commit 01c0321b17
15 changed files with 19 additions and 245 deletions

View File

@@ -211,7 +211,6 @@ createRoot(document.getElementById('container')).render(<Demo />);
'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,

View File

@@ -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(

View File

@@ -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<Omit<SiteContextProps, 'updateSiteContext'>>;
const RESPONSIVE_MOBILE = 768;

View File

@@ -74,7 +74,6 @@ exports[`antd exports modules correctly 1`] = `
"message",
"notification",
"theme",
"unstableSetRender",
"version",
]
`;

View File

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

View File

@@ -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<HTMLDivElement>(null);
// ====================== Refs ======================
const unmountRef = React.useRef<UnmountType>(null);
React.useEffect(() => {
unmountRef.current = registerUnmount();
}, []);
// ===================== Effect =====================
const [color, setWaveColor] = React.useState<string | null>(null);
const [borderRadius, setBorderRadius] = React.useState<number[]>([]);
@@ -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(
<WaveEffect {...info} target={target} registerUnmount={registerUnmount} />,
holder,
);
render(<WaveEffect {...info} target={target} />, holder);
};
export default showWaveEffect;

View File

@@ -1,29 +0,0 @@
import type * as React from 'react';
import { render, unmount } from '@rc-component/util/lib/React/render';
export type UnmountType = () => Promise<void>;
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;
}

View File

@@ -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';

View File

@@ -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(
<GlobalHolderWrapper
ref={(node) => {
const { instance, sync } = node || {};
// React 18 test env will throw if call immediately in ref
Promise.resolve().then(() => {
if (!newMessage.instance && instance) {

View File

@@ -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<typeof setTimeout>;
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 = <ConfirmDialogWrapper {...props} />;
const reactRender = unstableSetRender();
reactUnmount = reactRender(
render(
<ConfigProvider prefixCls={rootPrefixCls} iconPrefixCls={iconPrefixCls} theme={theme}>
{global.holderRender ? global.holderRender(dom) : dom}
{typeof global.holderRender === 'function' ? global.holderRender(dom) : dom}
</ConfigProvider>,
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);

View File

@@ -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(
<GlobalHolderWrapper
ref={(node) => {
const { instance, sync } = node || {};
Promise.resolve().then(() => {
if (!newNotification.instance && instance) {
newNotification.instance = instance;

View File

@@ -1,54 +0,0 @@
---
order: 1
title: React 19 Compatibility
tag: New
---
<!-- prettier-ignore -->
:::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
<InstallDependencies npm='npm install @ant-design/v5-patch-for-react-19 --save' yarn='yarn add @ant-design/v5-patch-for-react-19' pnpm='pnpm add @ant-design/v5-patch-for-react-19 --save' bun='bun add @ant-design/v5-patch-for-react-19'></InstallDependencies>
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();
};
});
```

View File

@@ -1,54 +0,0 @@
---
order: 1
title: React 19 兼容
tag: New
---
<!-- prettier-ignore -->
:::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 中正常工作。
### 兼容方式
以下方法任选其一,其中优先推荐使用兼容包。
#### 兼容包
安装兼容包
<InstallDependencies npm='npm install @ant-design/v5-patch-for-react-19 --save' yarn='yarn add @ant-design/v5-patch-for-react-19' pnpm='pnpm add @ant-design/v5-patch-for-react-19 --save' bun='bun add @ant-design/v5-patch-for-react-19'></InstallDependencies>
在应用入口处引入兼容包
```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();
};
});
```

View File

@@ -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",

View File

@@ -74,7 +74,6 @@ exports[`antd dist files exports modules correctly 1`] = `
"message",
"notification",
"theme",
"unstableSetRender",
"version",
]
`;