Skip to content

Commit

Permalink
feat: 支持同时在 React 16/17/18 下运行
Browse files Browse the repository at this point in the history
  • Loading branch information
lijinke666 committed Jul 3, 2024
1 parent 73b9b6c commit 67d6fc6
Show file tree
Hide file tree
Showing 6 changed files with 157 additions and 57 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,7 @@ export class DataCellClick extends BaseEvent implements BaseEventImplement {
operator,
hideSummary: true,
onlyShowCellText,
forceRender: true,
});
}

Expand Down
4 changes: 2 additions & 2 deletions packages/s2-react/playground/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -45,11 +45,11 @@ import {
import { debounce, isEmpty } from 'lodash';
import React from 'react';
import { ChromePicker } from 'react-color';
import { createRoot } from 'react-dom/client';
import reactPkg from '../package.json';
import type { SheetComponentOptions } from '../src';
import { SheetComponent } from '../src';
import { ConfigProvider } from '../src/components/config-provider';
import { reactRender } from '../src/utils/reactRender';
import { BigDataSheet } from './components/BigDataSheet';
import { ChartSheet } from './components/ChartSheet';
import { CustomGrid } from './components/CustomGrid';
Expand Down Expand Up @@ -1648,4 +1648,4 @@ function MainLayout() {
);
}

createRoot(document.getElementById('root')!).render(<MainLayout />);
reactRender(<MainLayout />, document.getElementById('root')!);
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {
GEvent,
S2Event,
S2_PREFIX_CLS,
SpreadSheet,
TableDataCell,
customMerge,
Expand Down Expand Up @@ -179,7 +180,7 @@ function EditCellComponent(
<Input.TextArea
required
style={styleProps}
className={'s2-edit-cell'}
className={`${S2_PREFIX_CLS}-edit-cell`}
value={inputVal as string}
ref={inputRef}
onChange={onChange}
Expand Down
39 changes: 4 additions & 35 deletions packages/s2-react/src/components/tooltip/custom-tooltip.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,8 @@
/* eslint-disable import/no-extraneous-dependencies */
// eslint-disable-next-line prettier/prettier
import { BaseTooltip, isMobile, SpreadSheet } from '@antv/s2';
import { startsWith } from 'lodash';
import React from 'react';
// eslint-disable-next-line react/no-deprecated
import { render, unmountComponentAtNode, version } from 'react-dom';
import { createRoot, type Root } from 'react-dom/client';
import { forceClear, reactRender, reactUnmount } from '../../utils/reactRender';
import { ConfigProvider } from '../config-provider';
import { TooltipComponent } from './index';
import type {
Expand All @@ -22,10 +19,6 @@ export class CustomTooltip extends BaseTooltip<
React.ReactNode,
TooltipOperatorMenuOptions
> {
root: Root | null;

isLegacyReactVersion = !startsWith(version, '18');

constructor(spreadsheet: SpreadSheet) {
super(spreadsheet);
}
Expand Down Expand Up @@ -61,14 +54,7 @@ export class CustomTooltip extends BaseTooltip<
</ConfigProvider>
);

if (this.isLegacyReactVersion) {
render(TooltipContent, this.container);

return;
}

this.root ??= createRoot(this.container!);
this.root.render(TooltipContent);
reactRender(TooltipContent, this.container!);
}

hide() {
Expand All @@ -84,27 +70,10 @@ export class CustomTooltip extends BaseTooltip<
}

forceClearContent() {
if (this.isLegacyReactVersion) {
this.unmount();

return;
}

this.root?.render(null);
forceClear(this.container!);
}

unmount() {
if (this.isLegacyReactVersion && this.container!) {
unmountComponentAtNode(this.container);

return;
}

// https://github.com/facebook/react/issues/25675#issuecomment-1363957941
Promise.resolve().then(() => {
this.root?.unmount();
// Fiber 节点卸载后不能再重新渲染, 需要重新创建
this.root = null;
});
reactUnmount(this.container!);
}
}
37 changes: 18 additions & 19 deletions packages/s2-react/src/utils/invokeComponent.tsx
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
import type { SpreadSheet } from '@antv/s2';
import React from 'react';
import { createRoot, type Root } from 'react-dom/client';
import { SpreadSheetContext } from '../context/SpreadSheetContext';
import { reactRender, reactUnmount } from './reactRender';

export type InvokeComponentProps<P> = {
onCancel: any;
resolver: (val: boolean) => any;
onCancel: () => void;
resolver: (val: boolean) => void;
params: P;
};

Expand All @@ -24,20 +24,19 @@ export function invokeComponent<P>(options: InvokeComponentOptions<P>) {
const { id, s2, params, onCleanup, component: Component } = options;

if (id) {
const domNode = document.querySelector(`#${id}`);
const container = document.querySelector(`#${id}`);

if (domNode) {
// const unmountResult = ReactDOM.unmountComponentAtNode(domNode);
if (container) {
const result = reactUnmount(container);

if (domNode.parentNode) {
domNode.parentNode.removeChild(domNode);
if (result && container.parentNode) {
container.parentNode.removeChild(container);

return;
}
}
}

let root: Root;
const container = document.createElement('div');

if (id) {
Expand All @@ -46,11 +45,12 @@ export function invokeComponent<P>(options: InvokeComponentOptions<P>) {

document.body.appendChild(container);

let resolveCb: (value: unknown) => void;
let resolveCb: (value: boolean) => void;
let rejectCb: (reason?: unknown) => void;

function destroy() {
root?.unmount();
reactUnmount(container);

if (container.parentNode) {
container.parentNode.removeChild(container);

Expand All @@ -60,33 +60,32 @@ export function invokeComponent<P>(options: InvokeComponentOptions<P>) {
}
}

function close() {
function onClose() {
destroy();
rejectCb();
}

const prom = new Promise((resolve, reject) => {
const handler = new Promise((resolve, reject) => {
resolveCb = resolve;
rejectCb = reject;
}).then((val) => {
close();
onClose();

return val;
});

function render() {
setTimeout(() => {
root = createRoot(container!);

root.render(
reactRender(
<SpreadSheetContext.Provider value={s2}>
<Component onCancel={close} resolver={resolveCb} params={params} />
<Component onCancel={onClose} resolver={resolveCb} params={params} />
</SpreadSheetContext.Provider>,
container,
);
});
}

render();

return prom;
return handler;
}
130 changes: 130 additions & 0 deletions packages/s2-react/src/utils/reactRender.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
/**
* 参考 react-component/util 同时兼容 React 16/17/18 的挂载和卸载
* @link https://github.com/react-component/util/blob/677d3ac177d147572b65af63e67a7796a5104f4c/src/React/render.ts
*/
import { version } from 'react';
import * as ReactDOM from 'react-dom';
import type { Root } from 'react-dom/client';

const REACT_ROOT_SYMBOL_ID = `__s2_react_root__`;

type ContainerType = (Element | DocumentFragment) & {
[REACT_ROOT_SYMBOL_ID]?: Root;
};

type CreateRoot = (container: ContainerType) => Root;

const ReactDOMClone = {
...ReactDOM,
} as typeof ReactDOM & {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED?: {
usingClientEntryPoint?: boolean;
};
createRoot?: CreateRoot;
};

const { render: reactOriginalRender, unmountComponentAtNode } = ReactDOMClone;

let createRoot: CreateRoot;

try {
const mainVersion = Number((version || '').split('.')[0]);

if (mainVersion >= 18) {
createRoot = ReactDOMClone.createRoot!;
}
} catch (e) {
// < React 18
}

export const isLegacyReactVersion = () => {
return !createRoot;
};

/**
* 由于兼容的关系, 没有从 "react-dom/client" 引入 createRoot, 会报 warning
* https://github.com/facebook/react/issues/24372
*/
function toggleWarning(skip: boolean) {
const { __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED } = ReactDOMClone;

if (
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED &&
typeof __SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED === 'object'
) {
__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED.usingClientEntryPoint =
skip;
}
}

// ========================== Render ==========================

function modernRender(
node: React.ReactElement | null,
container: ContainerType,
): Root {
toggleWarning(true);
const root = container[REACT_ROOT_SYMBOL_ID] || createRoot(container);

toggleWarning(false);

root.render(node);

container[REACT_ROOT_SYMBOL_ID] = root;

return root;
}

function legacyRender(
node: React.ReactElement | null,
container: ContainerType,
) {
reactOriginalRender(node!, container);
}

export function reactRender(
node: React.ReactElement | null,
container: ContainerType,
) {
if (!isLegacyReactVersion()) {
modernRender(node, container);

return;
}

legacyRender(node, container);
}

// ========================= Unmount ==========================
function modernUnmount(container: ContainerType) {
// https://github.com/facebook/react/issues/25675#issuecomment-1363957941
return Promise.resolve().then(() => {
container?.[REACT_ROOT_SYMBOL_ID]?.unmount();

delete container?.[REACT_ROOT_SYMBOL_ID];
});
}

function legacyUnmount(container: ContainerType) {
if (container) {
unmountComponentAtNode(container);
}
}

export function reactUnmount(container: ContainerType) {
if (!isLegacyReactVersion()) {
return modernUnmount(container);
}

return legacyUnmount(container);
}

export function forceClear(container: ContainerType) {
if (isLegacyReactVersion()) {
legacyUnmount(container);

return;
}

modernRender(null, container);
}

0 comments on commit 67d6fc6

Please sign in to comment.