Skip to content

Commit d820d69

Browse files
HaixingOoOHeisinganlyyao
authored
feat: support React19 (#606)
* feat(react): support React19 * fix(site): fix site display * chore: replace react-spring from motion * chore: update some devDependency versions * fix: fixed the problem of not being able to find the JSX namespace when packaging * fix: fix build * fix: remove build of react-dom/client * fix: fix type error * chore: use React.ReactElement instead of JSX.Element * chore: ts type enhancement * chore: fix compatibility under 18 * chore: ts type enhance * fix: fix build * chore: revert 7db014e * chore: fix cr --------- Co-authored-by: Heising <[email protected]> Co-authored-by: anlyyao <[email protected]>
1 parent a35634a commit d820d69

File tree

90 files changed

+651
-643
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

90 files changed

+651
-643
lines changed

package.json

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -88,13 +88,13 @@
8888
"@rollup/plugin-node-resolve": "^13.0.4",
8989
"@rollup/plugin-replace": "^3.0.0",
9090
"@rollup/plugin-url": "^6.0.0",
91-
"@testing-library/jest-dom": "^5.16.2",
92-
"@testing-library/react": "^12.1.4",
93-
"@testing-library/user-event": "^13.5.0",
91+
"@testing-library/jest-dom": "^6.6.3",
92+
"@testing-library/react": "^16.3.0",
93+
"@testing-library/user-event": "^14.6.1",
9494
"@types/lodash-es": "^4.17.12",
9595
"@types/node": "^22.14.1",
96-
"@types/react": "^17.0.2",
97-
"@types/react-dom": "17.0.2",
96+
"@types/react": "^19.1.0",
97+
"@types/react-dom": "^19.1.1",
9898
"@types/react-transition-group": "^4.4.4",
9999
"@types/rimraf": "^4.0.5",
100100
"@types/tinycolor2": "^1.4.6",
@@ -138,8 +138,8 @@
138138
"postcss": "^8.4.5",
139139
"prettier": "^3.3.3",
140140
"prismjs": "^1.25.0",
141-
"react": "^17.0.2",
142-
"react-dom": "^17.0.2",
141+
"react": "^19.1.0",
142+
"react-dom": "^19.1.0",
143143
"react-router-dom": "^6.2.2",
144144
"rimraf": "^6.0.1",
145145
"rollup": "^2.55.0",
@@ -159,13 +159,13 @@
159159
"vite": "^6.2.3",
160160
"vite-plugin-pwa": "^1.0.0",
161161
"vite-plugin-tdoc": "^2.0.4",
162-
"vitest": "^2.0.3",
162+
"vitest": "^2.1.9",
163163
"workbox-precaching": "^6.3.0"
164164
},
165165
"dependencies": {
166166
"@popperjs/core": "^2.11.8",
167167
"@use-gesture/react": "^10.2.10",
168-
"ahooks": "^3.1.9",
168+
"ahooks": "^3.8.5",
169169
"classnames": "^2.3.1",
170170
"dayjs": "^1.11.13",
171171
"hoist-non-react-statics": "^3.3.2",

script/rollup.config.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ import pkg from '../package.json';
1818

1919
const name = 'tdesign';
2020
const externalDeps = Object.keys(pkg.dependencies || {});
21-
const externalPeerDeps = Object.keys(pkg.peerDependencies || {});
21+
const externalPeerDeps = Object.keys(pkg.peerDependencies || {}).concat(['react-dom/client']);
2222
const banner = `/**
2323
* ${name} v${pkg.version}
2424
* (c) ${new Date().getFullYear()} ${pkg.author}

site/mobile/main.jsx

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,15 @@
11
import React from 'react';
2-
import ReactDOM from 'react-dom';
2+
import ReactDOM from 'react-dom/client';
33
import App from './App';
44
import '../style/mobile/index.less';
55

66
import '../../src/_common/style/mobile/_reset.less';
77
// import '../../src/_common/style/mobile/index.less';
88

9-
ReactDOM.render(
9+
const root = ReactDOM.createRoot(document.getElementById('app'));
10+
11+
root.render(
1012
<React.StrictMode>
1113
<App />
1214
</React.StrictMode>,
13-
document.getElementById('app'),
1415
);

site/web/main.jsx

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import React from 'react';
2-
import ReactDOM from 'react-dom';
2+
import ReactDOM from 'react-dom/client';
33

44
import { registerLocaleChange } from 'tdesign-site-components';
55
import 'tdesign-site-components/lib/styles/prism-theme-dark.less';
@@ -13,9 +13,11 @@ import App from './App';
1313
import '../style/web/index.less';
1414

1515
registerLocaleChange();
16-
ReactDOM.render(
16+
17+
const root = ReactDOM.createRoot(document.getElementById('app'));
18+
19+
root.render(
1720
<React.StrictMode>
1821
<App />
1922
</React.StrictMode>,
20-
document.getElementById('app'),
2123
);

src/_util/forwardRefWithStatics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,5 +5,5 @@ export default function forwardRefWithStatics<P, T = any, S = {}>(
55
component: React.ForwardRefRenderFunction<T, P>,
66
statics?: S,
77
): React.FunctionComponent<P & RefAttributes<T>> & S {
8-
return hoistNonReactStatics(forwardRef(component), statics as any) as any;
8+
return hoistNonReactStatics(forwardRef(component as any), statics as any) as any;
99
}

src/_util/react-render.ts

Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
// Implementation reference from: https://github.com/react-component/util/blob/master/src/React/render.ts
2+
// @ts-ignore
3+
import type * as React from 'react';
4+
import * as ReactDOM from 'react-dom';
5+
import { createRoot as createRootClient } from 'react-dom/client';
6+
import type { Root } from 'react-dom/client';
7+
8+
// Let compiler not to search module usage
9+
const fullClone = {
10+
...ReactDOM,
11+
} as typeof ReactDOM & {
12+
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
13+
usingClientEntryPoint?: boolean;
14+
};
15+
createRoot?: CreateRoot;
16+
};
17+
18+
type CreateRoot = (container: ContainerType) => Root;
19+
20+
// @ts-ignore
21+
const { version, render: reactRender, unmountComponentAtNode } = fullClone;
22+
23+
let createRoot: CreateRoot;
24+
try {
25+
const mainVersion = Number((version || '').split('.')[0]);
26+
if (mainVersion >= 18 && mainVersion < 19) {
27+
({ createRoot } = fullClone);
28+
} else if (mainVersion >= 19) {
29+
createRoot = createRootClient;
30+
}
31+
} catch (e) {
32+
// Do nothing;
33+
}
34+
35+
function toggleWarning(skip: boolean) {
36+
const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = fullClone;
37+
38+
if (
39+
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
40+
typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
41+
) {
42+
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint = skip;
43+
}
44+
}
45+
46+
const MARK = '__td_mobile_react_root__';
47+
48+
// ========================== Render ==========================
49+
type ContainerType = (Element | DocumentFragment) & {
50+
[MARK]?: Root;
51+
};
52+
53+
function modernRender(node: React.ReactElement, container: ContainerType) {
54+
toggleWarning(true);
55+
const root = container[MARK] || createRoot(container);
56+
toggleWarning(false);
57+
58+
root.render(node);
59+
60+
// eslint-disable-next-line
61+
container[MARK] = root;
62+
}
63+
64+
function legacyRender(node: React.ReactElement, container: ContainerType) {
65+
reactRender(node, container);
66+
}
67+
68+
export function render(node: React.ReactElement, container: ContainerType) {
69+
if (createRoot) {
70+
modernRender(node, container);
71+
return;
72+
}
73+
74+
legacyRender(node, container);
75+
}
76+
77+
// ========================= Unmount ==========================
78+
async function modernUnmount(container: ContainerType) {
79+
// Delay to unmount to avoid React 18 sync warning
80+
return Promise.resolve().then(() => {
81+
container[MARK]?.unmount();
82+
83+
// eslint-disable-next-line
84+
delete container[MARK];
85+
});
86+
}
87+
88+
function legacyUnmount(container: ContainerType) {
89+
unmountComponentAtNode(container);
90+
}
91+
92+
export async function unmount(container: ContainerType) {
93+
if (createRoot !== undefined) {
94+
// Delay to unmount to avoid React 18 sync warning
95+
return modernUnmount(container);
96+
}
97+
98+
legacyUnmount(container);
99+
}

src/_util/renderToBody.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,16 @@
1-
import ReactDOM from 'react-dom';
21
import { ReactElement } from 'react';
2+
import { render, unmount as unMount } from './react-render';
33

44
export function renderToBody(element: ReactElement) {
55
const container = document.createElement('div');
66
document.body.appendChild(container);
77
function unmount() {
8-
const unmountResult = ReactDOM.unmountComponentAtNode(container);
8+
const unmountResult = unMount(container);
99
if (unmountResult && container.parentNode) {
1010
container.parentNode.removeChild(container);
1111
}
1212
}
13-
ReactDOM.render(element, container);
13+
render(element, container);
1414
return unmount;
1515
}
1616

src/_util/withNativeProps.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export interface NativeProps<S extends string = never> {
88
style?: CSSProperties & Partial<Record<S, string>>;
99
}
1010

11-
export default function withNativeProps<P extends NativeProps>(props: P, element: ReactElement) {
11+
export default function withNativeProps<P extends NativeProps>(props: P, element: ReactElement<P>) {
1212
const elementProps = element.props;
1313
const nativeProps: NativeProps & Record<string, any> = {};
1414
if (props.className) {

src/_util/withStopPropagation.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const eventToPropRecord: Record<PropagationEvent, string> = {
1010
[PropagationEvent.SCROLL]: 'onScroll',
1111
};
1212

13-
export function withStopPropagation(events: PropagationEvent[], element: ReactElement) {
13+
export function withStopPropagation(events: PropagationEvent[], element: ReactElement<any>) {
1414
const props: Record<string, any> = { ...element.props };
1515
if (!events.length) return element;
1616

src/action-sheet/ActionSheetGrid.tsx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import cx from 'classnames';
33

44
import type { ActionSheetProps } from './ActionSheet';
55
import type { ActionSheetItem } from './type';
6-
6+
import type { TNode } from '../common';
77
import { Grid, GridItem } from '../grid';
88
import { Swiper, SwiperProps } from '../swiper';
99
import { usePrefixClass } from '../hooks/useClass';
@@ -57,7 +57,7 @@ export function ActionSheetGrid(props: ActionSheetGridProps) {
5757
<Grid gutter={0} column={gridColumn} style={{ width: '100%' }}>
5858
{item.map((it, idx2) => {
5959
let label: string;
60-
let image: React.ReactNode;
60+
let image: TNode;
6161
let badge: ActionSheetItem['badge'];
6262
if (typeof it === 'string') {
6363
label = it;

0 commit comments

Comments
 (0)