From a80e252a2071f17ce458720cb162e9539e654bf3 Mon Sep 17 00:00:00 2001 From: bubucuo Date: Mon, 23 Dec 2024 07:20:39 -0800 Subject: [PATCH 1/7] refactor(Watermark): change useState to useRef (#52089) --- components/watermark/useWatermark.ts | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/components/watermark/useWatermark.ts b/components/watermark/useWatermark.ts index 2c7a3e9f19..2ef5a2ae5b 100644 --- a/components/watermark/useWatermark.ts +++ b/components/watermark/useWatermark.ts @@ -27,16 +27,16 @@ export default function useWatermark( removeWatermark: (container: HTMLElement) => void, isWatermarkEle: (ele: Node) => boolean, ] { - const [watermarkMap] = React.useState(() => new Map()); + const watermarkMap = React.useRef(new Map()); const appendWatermark = (base64Url: string, markWidth: number, container: HTMLElement) => { if (container) { - if (!watermarkMap.get(container)) { + if (!watermarkMap.current.get(container)) { const newWatermarkEle = document.createElement('div'); - watermarkMap.set(container, newWatermarkEle); + watermarkMap.current.set(container, newWatermarkEle); } - const watermarkEle = watermarkMap.get(container)!; + const watermarkEle = watermarkMap.current.get(container)!; watermarkEle.setAttribute( 'style', @@ -55,20 +55,20 @@ export default function useWatermark( } } - return watermarkMap.get(container); + return watermarkMap.current.get(container); }; const removeWatermark = (container: HTMLElement) => { - const watermarkEle = watermarkMap.get(container); + const watermarkEle = watermarkMap.current.get(container); if (watermarkEle && container) { container.removeChild(watermarkEle); } - watermarkMap.delete(container); + watermarkMap.current.delete(container); }; - const isWatermarkEle = (ele: any) => Array.from(watermarkMap.values()).includes(ele); + const isWatermarkEle = (ele: any) => Array.from(watermarkMap.current.values()).includes(ele); return [appendWatermark, removeWatermark, isWatermarkEle]; } From 1114fb2aad77398156babb6536d0b22a7870889e Mon Sep 17 00:00:00 2001 From: afc163 Date: Tue, 24 Dec 2024 11:46:17 +0800 Subject: [PATCH 2/7] fix: reactRender is not a function in React 19 (#52105) Signed-off-by: afc163 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 31ffdfd7fb..8f361278ac 100644 --- a/package.json +++ b/package.json @@ -151,7 +151,7 @@ "rc-tree": "~5.10.1", "rc-tree-select": "~5.24.5", "rc-upload": "~4.8.1", - "rc-util": "^5.44.2", + "rc-util": "^5.44.3", "scroll-into-view-if-needed": "^3.1.0", "throttle-debounce": "^5.0.2" }, From 295a4c701651c92aa096ad4e27e35cd41c95b8e7 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:00:51 +0800 Subject: [PATCH 3/7] chore(deps): update dependency typedoc to ^0.27.0 (#51845) * chore(deps): update dependency typedoc to ^0.27.0 * Update package.json Signed-off-by: afc163 --------- Signed-off-by: afc163 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: afc163 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 8f361278ac..b8585354c2 100644 --- a/package.json +++ b/package.json @@ -315,7 +315,7 @@ "tar-fs": "^3.0.6", "terser": "^5.36.0", "tsx": "4.11.2", - "typedoc": "~0.26.11", + "typedoc": "^0.27.0", "typescript": "~5.7.0", "vanilla-jsoneditor": "^2.0.0", "vanilla-tilt": "^1.8.1", From 7b04758b313052e524c9196a71a40812e0851944 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 17:26:13 +0800 Subject: [PATCH 4/7] chore(deps): update dependency tsx to v4.19.2 (#52108) * chore(deps): update dependency tsx to v4.19.2 * Update package.json Signed-off-by: afc163 --------- Signed-off-by: afc163 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: afc163 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b8585354c2..7d16ef8ffc 100644 --- a/package.json +++ b/package.json @@ -314,7 +314,7 @@ "tar": "^7.4.3", "tar-fs": "^3.0.6", "terser": "^5.36.0", - "tsx": "4.11.2", + "tsx": "^4.19.2", "typedoc": "^0.27.0", "typescript": "~5.7.0", "vanilla-jsoneditor": "^2.0.0", From 4985db9d561ea85f00c9a7b791c5e4d0c5c664f1 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:01:23 +0800 Subject: [PATCH 5/7] chore(deps): update dependency eslint-plugin-react-hooks to v5.2.0 (#51937) * chore(deps): update dependency eslint-plugin-react-hooks to v5.1.0 * Update package.json Signed-off-by: afc163 --------- Signed-off-by: afc163 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: afc163 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 7d16ef8ffc..e4dbf112df 100644 --- a/package.json +++ b/package.json @@ -236,7 +236,7 @@ "eslint-plugin-compat": "^6.0.1", "eslint-plugin-jest": "^28.8.3", "eslint-plugin-jsx-a11y": "^6.10.0", - "eslint-plugin-react-hooks": "5.0.0", + "eslint-plugin-react-hooks": "^5.2.0-canary-6907aa2a-20241220", "eslint-plugin-react-refresh": "^0.4.13", "fast-glob": "^3.3.2", "fetch-jsonp": "^1.3.0", From ff52bbcc865cb9b0ea61931c92cc110151007e8a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 24 Dec 2024 18:27:18 +0800 Subject: [PATCH 6/7] chore(deps): update dependency chalk to v5 (#52109) * chore(deps): update dependency chalk to v5 * migrate chalk * Update .github/workflows/test.yml Signed-off-by: afc163 --------- Signed-off-by: afc163 Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com> Co-authored-by: afc163 --- .dumi/theme/plugin.ts | 4 ++-- .github/workflows/test.yml | 2 +- package.json | 4 ++-- scripts/test-all.sh | 4 ++-- tests/dekko/{dist.test.js => dist.test.ts} | 4 ++-- tests/dekko/index.test.js | 3 --- tests/dekko/index.test.ts | 3 +++ tests/dekko/lib-es.test.js | 24 ------------------- tests/dekko/lib-es.test.ts | 28 ++++++++++++++++++++++ tests/dekko/use-client.test.js | 28 ---------------------- tests/dekko/use-client.test.ts | 28 ++++++++++++++++++++++ typings/custom-typings.d.ts | 2 ++ 12 files changed, 70 insertions(+), 64 deletions(-) rename tests/dekko/{dist.test.js => dist.test.ts} (86%) delete mode 100644 tests/dekko/index.test.js create mode 100644 tests/dekko/index.test.ts delete mode 100644 tests/dekko/lib-es.test.js create mode 100644 tests/dekko/lib-es.test.ts delete mode 100644 tests/dekko/use-client.test.js create mode 100644 tests/dekko/use-client.test.ts diff --git a/.dumi/theme/plugin.ts b/.dumi/theme/plugin.ts index 78b2de1f78..b668f48c1d 100644 --- a/.dumi/theme/plugin.ts +++ b/.dumi/theme/plugin.ts @@ -2,7 +2,6 @@ import { createHash } from 'crypto'; import fs from 'fs'; import path from 'path'; import createEmotionServer from '@emotion/server/create-instance'; -import chalk from 'chalk'; import type { IApi, IRoute } from 'dumi'; import ReactTechStack from 'dumi/dist/techStacks/react'; import sylvanas from 'sylvanas'; @@ -126,7 +125,8 @@ class AntdReactTechStack extends ReactTechStack { const resolve = (p: string): string => require.resolve(p); -const RoutesPlugin = (api: IApi) => { +const RoutesPlugin = async (api: IApi) => { + const chalk = await import('chalk').then((m) => m.default); // const ssrCssFileName = `ssr-${Date.now()}.css`; const writeCSSFile = (key: string, hashKey: string, cssString: string) => { diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 411387d53a..662d7d09da 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -175,7 +175,7 @@ jobs: CI: 1 - name: check build files - run: node ./tests/dekko/index.test.js + run: bun run test:dekko # Artifact build files - uses: actions/upload-artifact@v4 diff --git a/package.json b/package.json index e4dbf112df..33d33a27ce 100644 --- a/package.json +++ b/package.json @@ -83,7 +83,7 @@ "pretest": "npm run version", "test": "jest --config .jest.js --no-cache", "test:all": "sh -e ./scripts/test-all.sh", - "test:dekko": "node ./tests/dekko/index.test.js", + "test:dekko": "tsx ./tests/dekko/index.test.ts", "test:image": "jest --config .jest.image.js --no-cache -i -u --forceExit", "test:node": "npm run version && jest --config .jest.node.js --no-cache", "test:package-diff": "antd-tools run package-diff", @@ -222,7 +222,7 @@ "antd-style": "^3.7.0", "antd-token-previewer": "^2.0.8", "axios": "^1.7.7", - "chalk": "^4.1.2", + "chalk": "^5.0.0", "cheerio": "^1.0.0", "circular-dependency-plugin": "^5.2.2", "cli-progress": "^3.12.0", diff --git a/scripts/test-all.sh b/scripts/test-all.sh index 6abba21700..c0ba9b5f35 100755 --- a/scripts/test-all.sh +++ b/scripts/test-all.sh @@ -58,11 +58,11 @@ fi if ! has_arg '--skip-dekko' "$@"; then echo "[TEST ALL] dekko dist" echo "[TEST ALL] dekko dist" > ~test-all.txt - node ./tests/dekko/dist.test.js + tsx ./tests/dekko/dist.test.ts echo "[TEST ALL] dekko lib and es" echo "[TEST ALL] dekko lib and es" > ~test-all.txt - node ./tests/dekko/lib-es.test.js + tsx ./tests/dekko/lib-es.test.ts else echo "[TEST ALL] dekko test...skip" fi diff --git a/tests/dekko/dist.test.js b/tests/dekko/dist.test.ts similarity index 86% rename from tests/dekko/dist.test.js rename to tests/dekko/dist.test.ts index 326646c53e..4cf7e4145a 100644 --- a/tests/dekko/dist.test.js +++ b/tests/dekko/dist.test.ts @@ -1,5 +1,5 @@ -const $ = require('dekko'); -const chalk = require('chalk'); +import $ from 'dekko'; +import chalk from 'chalk'; $('dist') .isDirectory() diff --git a/tests/dekko/index.test.js b/tests/dekko/index.test.js deleted file mode 100644 index 8160b09099..0000000000 --- a/tests/dekko/index.test.js +++ /dev/null @@ -1,3 +0,0 @@ -require('./dist.test'); -require('./lib-es.test'); -require('./use-client.test'); diff --git a/tests/dekko/index.test.ts b/tests/dekko/index.test.ts new file mode 100644 index 0000000000..bbae682c34 --- /dev/null +++ b/tests/dekko/index.test.ts @@ -0,0 +1,3 @@ +import './dist.test'; +import './lib-es.test'; +import './use-client.test'; diff --git a/tests/dekko/lib-es.test.js b/tests/dekko/lib-es.test.js deleted file mode 100644 index 0a1fad2c3d..0000000000 --- a/tests/dekko/lib-es.test.js +++ /dev/null @@ -1,24 +0,0 @@ -const $ = require('dekko'); -const chalk = require('chalk'); - -$('lib').isDirectory().hasFile('index.js').hasFile('index.d.ts'); - -$('lib/*') - .filter((filename) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext))) - .isDirectory() - .filter((filename) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext))) - .hasFile('index.js') - .hasFile('index.d.ts'); - -console.log(chalk.green('✨ `lib` directory is valid.')); - -$('es').isDirectory().hasFile('index.js').hasFile('index.d.ts'); - -$('es/*') - .filter((filename) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext))) - .isDirectory() - .filter((filename) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext))) - .hasFile('index.js') - .hasFile('index.d.ts'); - -console.log(chalk.green('✨ `es` directory is valid.')); diff --git a/tests/dekko/lib-es.test.ts b/tests/dekko/lib-es.test.ts new file mode 100644 index 0000000000..61e55ea5d6 --- /dev/null +++ b/tests/dekko/lib-es.test.ts @@ -0,0 +1,28 @@ +import $ from 'dekko'; +import chalk from 'chalk'; + +$('lib').isDirectory().hasFile('index.js').hasFile('index.d.ts'); + +$('lib/*') + .filter( + (filename: string) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext)), + ) + .isDirectory() + .filter((filename: string) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext))) + .hasFile('index.js') + .hasFile('index.d.ts'); + +console.log(chalk.green('✨ `lib` directory is valid.')); + +$('es').isDirectory().hasFile('index.js').hasFile('index.d.ts'); + +$('es/*') + .filter( + (filename: string) => !['index.js', 'index.d.ts', '.map'].some((ext) => filename.endsWith(ext)), + ) + .isDirectory() + .filter((filename: string) => !['style', '_util', 'locale'].some((ext) => filename.endsWith(ext))) + .hasFile('index.js') + .hasFile('index.d.ts'); + +console.log(chalk.green('✨ `es` directory is valid.')); diff --git a/tests/dekko/use-client.test.js b/tests/dekko/use-client.test.js deleted file mode 100644 index 243318d396..0000000000 --- a/tests/dekko/use-client.test.js +++ /dev/null @@ -1,28 +0,0 @@ -const $ = require('dekko'); -const chalk = require('chalk'); -const fs = require('fs'); - -const includeUseClient = (filename) => - fs.readFileSync(filename).toString().includes('"use client"'); - -$('dist/*') - .isFile() - .assert("doesn't contain use client", (filename) => !includeUseClient(filename)); -$('{es,lib}/index.js') - .isFile() - .assert('contain use client', (filename) => includeUseClient(filename)); -$('{es,lib}/*/index.js') - .isFile() - .assert('contain use client', (filename) => includeUseClient(filename)); - -// check tsx files -$('{es,lib}/typography/*.js') - .isFile() - .assert('contain use client', (filename) => includeUseClient(filename)); - -$('{es,lib}/typography/Base/*.js') - .isFile() - .filter((filename) => !filename.endsWith('/util.js')) - .assert('contain use client', (filename) => includeUseClient(filename)); - -console.log(chalk.green('✨ use client passed!')); diff --git a/tests/dekko/use-client.test.ts b/tests/dekko/use-client.test.ts new file mode 100644 index 0000000000..0bf680f043 --- /dev/null +++ b/tests/dekko/use-client.test.ts @@ -0,0 +1,28 @@ +import $ from 'dekko'; +import chalk from 'chalk'; +import fs from 'node:fs'; + +const includeUseClient = (filename: string) => + fs.readFileSync(filename).toString().includes('"use client"'); + +$('dist/*') + .isFile() + .assert("doesn't contain use client", (filename: string) => !includeUseClient(filename)); +$('{es,lib}/index.js') + .isFile() + .assert('contain use client', (filename: string) => includeUseClient(filename)); +$('{es,lib}/*/index.js') + .isFile() + .assert('contain use client', (filename: string) => includeUseClient(filename)); + +// check tsx files +$('{es,lib}/typography/*.js') + .isFile() + .assert('contain use client', (filename: string) => includeUseClient(filename)); + +$('{es,lib}/typography/Base/*.js') + .isFile() + .filter((filename: string) => !filename.endsWith('/util.js')) + .assert('contain use client', (filename: string) => includeUseClient(filename)); + +console.log(chalk.green('✨ use client passed!')); diff --git a/typings/custom-typings.d.ts b/typings/custom-typings.d.ts index 2a12b0bd35..758a40db17 100644 --- a/typings/custom-typings.d.ts +++ b/typings/custom-typings.d.ts @@ -27,3 +27,5 @@ declare module '@npmcli/run-script' { } declare module '@microflash/rehype-figure'; + +declare module 'dekko'; From f8115d3b6b3faae3e45142d9c40e8e74222b7762 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=BA=8C=E8=B4=A7=E7=88=B1=E5=90=83=E7=99=BD=E8=90=9D?= =?UTF-8?q?=E5=8D=9C?= Date: Tue, 24 Dec 2024 19:31:22 +0800 Subject: [PATCH 7/7] docs: Update for React 19 compatible info (#52111) * chore: init docs * docs: update * docs: update with compatible * test: add test case * docs: update docs * chore: bump patch version * test: fix test case * chore: fix warning check logic --- .dumi/theme/layouts/GlobalLayout.tsx | 13 +---- .../config-provider/UnstableContext.tsx | 19 ++++++ .../__tests__/unstable.test.tsx | 32 ++++++++++ docs/react/migration-v5.zh-CN.md | 1 - docs/react/v5-for-19.en-US.md | 58 +++++++++++++++++++ docs/react/v5-for-19.zh-CN.md | 58 +++++++++++++++++++ package.json | 1 + 7 files changed, 171 insertions(+), 11 deletions(-) create mode 100644 components/config-provider/__tests__/unstable.test.tsx create mode 100644 docs/react/v5-for-19.en-US.md create mode 100644 docs/react/v5-for-19.zh-CN.md diff --git a/.dumi/theme/layouts/GlobalLayout.tsx b/.dumi/theme/layouts/GlobalLayout.tsx index f4df0cd1c8..6453650d76 100644 --- a/.dumi/theme/layouts/GlobalLayout.tsx +++ b/.dumi/theme/layouts/GlobalLayout.tsx @@ -9,11 +9,10 @@ import { } from '@ant-design/cssinjs'; import { HappyProvider } from '@ant-design/happy-work-theme'; import { getSandpackCssText } from '@codesandbox/sandpack-react'; -import { theme as antdTheme, App, unstableSetRender } from 'antd'; +import { theme as antdTheme, App } from 'antd'; import type { MappingAlgorithm } from 'antd'; import type { DirectionType, ThemeConfig } from 'antd/es/config-provider'; import { createSearchParams, useOutlet, useSearchParams, useServerInsertedHTML } from 'dumi'; -import { createRoot } from 'react-dom/client'; import { DarkContext } from '../../hooks/useDark'; import useLayoutState from '../../hooks/useLayoutState'; @@ -23,6 +22,8 @@ import SiteThemeProvider from '../SiteThemeProvider'; import type { SiteContextProps } from '../slots/SiteContext'; import SiteContext from '../slots/SiteContext'; +import '@ant-design/v5-patch-for-react-19'; + const ThemeSwitch = React.lazy(() => import('../common/ThemeSwitch')); type Entries = { [K in keyof T]: [K, T[K]] }[keyof T][]; @@ -31,14 +32,6 @@ type SiteState = Partial>; const RESPONSIVE_MOBILE = 768; export const ANT_DESIGN_NOT_SHOW_BANNER = 'ANT_DESIGN_NOT_SHOW_BANNER'; -unstableSetRender((node, container) => { - const root = createRoot(container); - root.render(node); - return async () => { - root.unmount(); - }; -}); - // const styleCache = createCache(); // if (typeof global !== 'undefined') { // (global as any).styleCache = styleCache; diff --git a/components/config-provider/UnstableContext.tsx b/components/config-provider/UnstableContext.tsx index ef1e161644..060ffb8a19 100644 --- a/components/config-provider/UnstableContext.tsx +++ b/components/config-provider/UnstableContext.tsx @@ -1,6 +1,9 @@ import * as React from 'react'; +import * as ReactDOM from 'react-dom'; import { render, unmount } from 'rc-util/lib/React/render'; +import warning from '../_util/warning'; + export type UnmountType = () => Promise; export type RenderType = ( node: React.ReactElement, @@ -8,6 +11,22 @@ export type RenderType = ( ) => UnmountType; const defaultReactRender: RenderType = (node, container) => { + // TODO: Remove in v6 + // Warning for React 19 + if (process.env.NODE_ENV !== 'production') { + const majorVersion = parseInt(React.version.split('.')[0], 10); + warning( + majorVersion < 19 || + !!( + ReactDOM as typeof ReactDOM & { + createRoot: VoidFunction; + } + ).createRoot, + 'compatible', + 'antd v5 support React is 16 ~ 18. see https://u.ant.design/v5-for-19 for compatible.', + ); + } + render(node, container); return () => { return unmount(container); diff --git a/components/config-provider/__tests__/unstable.test.tsx b/components/config-provider/__tests__/unstable.test.tsx new file mode 100644 index 0000000000..1b86119628 --- /dev/null +++ b/components/config-provider/__tests__/unstable.test.tsx @@ -0,0 +1,32 @@ +import { version } from 'react'; + +import { waitFakeTimer19 } from '../../../tests/utils'; +import Modal from '../../modal'; + +jest.mock('rc-util/lib/Dom/isVisible', () => () => true); + +describe('UnstableContext', () => { + beforeEach(() => { + jest.useFakeTimers(); + }); + + afterEach(() => { + jest.useRealTimers(); + }); + + // TODO: Remove in v6 + it('should warning', async () => { + const majorVersion = parseInt(version.split('.')[0], 10); + + if (majorVersion >= 19) { + const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); + Modal.info({ title: 'title', content: 'content' }); + + await waitFakeTimer19(); + + expect(errorSpy).toHaveBeenCalledWith( + 'Warning: [antd: compatible] antd v5 support React is 16 ~ 18. see https://u.ant.design/v5-for-19 for compatible.', + ); + } + }); +}); diff --git a/docs/react/migration-v5.zh-CN.md b/docs/react/migration-v5.zh-CN.md index 06eeac75c9..d378225d2f 100644 --- a/docs/react/migration-v5.zh-CN.md +++ b/docs/react/migration-v5.zh-CN.md @@ -4,7 +4,6 @@ group: order: 2 order: 0 title: 从 v4 到 v5 -tag: Updated --- 本文档将帮助你从 antd `4.x` 版本升级到 antd `5.x` 版本,如果你是 `3.x` 或者更老的版本,请先参考之前的[升级文档](https://4x.ant.design/docs/react/migration-v4-cn)升级到 4.x。 diff --git a/docs/react/v5-for-19.en-US.md b/docs/react/v5-for-19.en-US.md new file mode 100644 index 0000000000..8e9b7d9f61 --- /dev/null +++ b/docs/react/v5-for-19.en-US.md @@ -0,0 +1,58 @@ +--- +group: + title: Advanced +order: 9 +title: React 19 Compatibility +tag: New +--- + + +:::info{title="Compatibility Interface"} +antd v5 compatibility with React 16 ~ 18 by default. For React 19, you can use the following compatibility methods to adapt. +::: + +### React 19 Compatibility Issues + +Since React 19 adjusted the export method of `react-dom`, antd cannot directly use the `ReactDOM.render` method. Therefore, using antd will encounter the following problems: + +- Wave effect does not show +- Static methods of `Modal`, `Notification`, `Message` not working + +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 + +```bash +npm install --save-dev @ant-design/v5-patch-for-react-19 +``` + +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 new file mode 100644 index 0000000000..8057ee10cf --- /dev/null +++ b/docs/react/v5-for-19.zh-CN.md @@ -0,0 +1,58 @@ +--- +group: + title: 进阶使用 +order: 9 +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` 等组件的静态方法无效 + +因而需要通过兼容配置,使 antd 在 React 19 中正常工作。 + +### 兼容方式 + +以下方法任选其一,其中优先推荐使用兼容包。 + +#### 兼容包 + +安装兼容包 + +```bash +npm install --save-dev @ant-design/v5-patch-for-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 33d33a27ce..2cd96ea5ff 100644 --- a/package.json +++ b/package.json @@ -159,6 +159,7 @@ "@ant-design/compatible": "^5.1.3", "@ant-design/happy-work-theme": "^1.0.0", "@ant-design/tools": "^18.0.2", + "@ant-design/v5-patch-for-react-19": "^1.0.2", "@antfu/eslint-config": "^3.8.0", "@antv/g6": "^4.8.24", "@biomejs/biome": "^1.9.4",