From 1980162bf9b2cd10144899446981945c5fd89919 Mon Sep 17 00:00:00 2001 From: fangsmile <892739385@qq.com> Date: Fri, 31 Jul 2026 15:54:30 +0800 Subject: [PATCH] fix(react-vtable): lazy load custom layout reconciler --- packages/react-vtable/demo/src/App.tsx | 2 + .../list-table/issue-5203-vite-react19.tsx | 45 +++++++++++ .../demo/vite.config.issue5203.ts | 38 ++++++++++ .../table-components/custom/custom-layout.tsx | 75 ++++++++++++++++--- 4 files changed, 149 insertions(+), 11 deletions(-) create mode 100644 packages/react-vtable/demo/src/list-table/issue-5203-vite-react19.tsx create mode 100644 packages/react-vtable/demo/vite.config.issue5203.ts diff --git a/packages/react-vtable/demo/src/App.tsx b/packages/react-vtable/demo/src/App.tsx index 50c12c4cee..d4c9a43699 100644 --- a/packages/react-vtable/demo/src/App.tsx +++ b/packages/react-vtable/demo/src/App.tsx @@ -1,4 +1,5 @@ import listTable from './list-table/list-table'; +import issue5203ViteReact19 from './list-table/issue-5203-vite-react19'; import listOptionRecord from './list-table/list-option-records'; import listComponent from './list-table/list-component'; import listCustomLayout from './list-table/list-custom-layout'; @@ -32,6 +33,7 @@ import { Component, useEffect, useMemo, useState } from 'react'; declare const globalThis: any; const demoList = [ + { key: 'issue5203ViteReact19', Comp: issue5203ViteReact19 }, { key: 'listTable', Comp: listTable }, { key: 'listEditor', Comp: listEditor }, { key: 'listOptionRecord', Comp: listOptionRecord }, diff --git a/packages/react-vtable/demo/src/list-table/issue-5203-vite-react19.tsx b/packages/react-vtable/demo/src/list-table/issue-5203-vite-react19.tsx new file mode 100644 index 0000000000..4686cfa513 --- /dev/null +++ b/packages/react-vtable/demo/src/list-table/issue-5203-vite-react19.tsx @@ -0,0 +1,45 @@ +import { ListTable } from '../../../src'; + +declare const globalThis: any; + +const columns = [ + { + field: 'id', + title: 'ID', + width: 120 + }, + { + field: 'name', + title: 'Name', + width: 200 + }, + { + field: 'age', + title: 'Age', + width: 120 + } +]; + +const records = [ + { id: 1, name: 'Alice', age: 28 }, + { id: 2, name: 'Bob', age: 31 }, + { id: 3, name: 'Carol', age: 24 } +]; + +export default function Issue5203ViteReact19() { + return ( + { + (globalThis as any).tableInstance = table; + (globalThis as any).__issue5203Ready = true; + }} + onError={error => { + (globalThis as any).__issue5203Error = error; + }} + /> + ); +} diff --git a/packages/react-vtable/demo/vite.config.issue5203.ts b/packages/react-vtable/demo/vite.config.issue5203.ts new file mode 100644 index 0000000000..ad5ee89152 --- /dev/null +++ b/packages/react-vtable/demo/vite.config.issue5203.ts @@ -0,0 +1,38 @@ +import { defineConfig } from 'vite'; +import react from '@vitejs/plugin-react'; +import path from 'path'; +import { fileURLToPath } from 'url'; +import { createRequire } from 'module'; + +const require = createRequire(import.meta.url); +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); + +const react19Root = path.resolve(__dirname, '../../../.react19-deps/node_modules'); + +export default defineConfig({ + plugins: [react({ fastRefresh: false })] as any, + define: { + __DEV__: true, + __VERSION__: JSON.stringify(require('../../vtable/package.json').version) + }, + server: { + host: '0.0.0.0', + port: 3102 + }, + resolve: { + alias: [ + { find: /^react$/, replacement: path.resolve(react19Root, 'react/index.js') }, + { find: /^react\/jsx-runtime(\.js)?$/, replacement: path.resolve(react19Root, 'react/jsx-runtime.js') }, + { find: /^react\/jsx-dev-runtime(\.js)?$/, replacement: path.resolve(react19Root, 'react/jsx-dev-runtime.js') }, + { find: /^react-dom$/, replacement: path.resolve(react19Root, 'react-dom/index.js') }, + { find: /^react-dom\/client(\.js)?$/, replacement: path.resolve(react19Root, 'react-dom/client.js') }, + { find: '@visactor/vtable/es/vrender', replacement: path.resolve(__dirname, '../../vtable/src/vrender.ts') }, + { find: '@visactor/vtable/es', replacement: path.resolve(__dirname, '../../vtable/src/') }, + { find: '@visactor/vtable', replacement: path.resolve(__dirname, '../../vtable/src/index.ts') }, + { find: '@visactor/vtable-plugins', replacement: path.resolve(__dirname, '../../vtable-plugins/src/index.ts') }, + { find: '@src', replacement: path.resolve(__dirname, '../../vtable/src/') }, + { find: '@vutils-extension', replacement: path.resolve(__dirname, '../../vtable/src/vutil-extension-temp') } + ] + } +}); diff --git a/packages/react-vtable/src/table-components/custom/custom-layout.tsx b/packages/react-vtable/src/table-components/custom/custom-layout.tsx index 1bf72f7dae..9731ded216 100644 --- a/packages/react-vtable/src/table-components/custom/custom-layout.tsx +++ b/packages/react-vtable/src/table-components/custom/custom-layout.tsx @@ -1,12 +1,11 @@ /* eslint-disable react-hooks/rules-of-hooks */ import type { PropsWithChildren, ReactElement } from 'react'; -import React, { isValidElement, useCallback, useContext, useLayoutEffect, useRef } from 'react'; +import React, { isValidElement, useCallback, useContext, useLayoutEffect, useRef, useState } from 'react'; import RootTableContext from '../../context/table'; import { Group } from '@visactor/vtable/es/vrender'; import type { ICustomLayoutFuc, CustomRenderFunctionArg } from '@visactor/vtable/es/ts-types'; import type { FiberRoot } from 'react-reconciler'; -import type { ReconcilerErrorReporter } from './reconciler'; -import { reconcilor, createReconcilerContainer } from './reconciler'; +import type { ReconcilerErrorReporter, ReconcilerErrorType } from './reconciler'; type CustomLayoutProps = { componentId: string }; @@ -22,6 +21,8 @@ export const CustomLayout: React.FC = (props: PropsWithChildr } const context = useContext(RootTableContext); const { table, onError } = context; + const [reconcilerReady, setReconcilerReady] = useState(false); + const reconcilerModule = useRef(null); const isHeaderCustomLayout = children.props.role === 'header-custom-layout'; @@ -45,22 +46,48 @@ export const CustomLayout: React.FC = (props: PropsWithChildr [onError] ); + useLayoutEffect(() => { + let released = false; + // Load the custom-layout reconciler only when CustomLayout is actually used. + import('./reconciler') + .then(module => { + if (released) { + return; + } + reconcilerModule.current = module; + setReconcilerReady(true); + }) + .catch(error => { + reportReconcilerError('uncaught', error); + }); + return () => { + released = true; + }; + }, [reportReconcilerError]); + // customLayout function for vtable const createGraphic: ICustomLayoutFuc = useCallback( - args => { + (args: any) => { + const module = reconcilerModule.current; + if (!module) { + return { + rootContainer: new Group({}), + renderDefault: !!children.props.renderDefault + }; + } const key = `${args.originCol ?? args.col}-${args.originRow ?? args.row}${ args.forComputation ? '-forComputation' : '' }`; let group; if (container.current.has(key)) { const currentContainer = container.current.get(key); - reconcilorUpdateContainer(children, currentContainer, args); + reconcilorUpdateContainer(module, children, currentContainer, args); group = currentContainer.containerInfo; } else { group = new Group({}); - const currentContainer = createReconcilerContainer(group as any, 'custom', reportReconcilerError); + const currentContainer = module.createReconcilerContainer(group as any, 'custom', reportReconcilerError); container.current.set(key, currentContainer); - reconcilorUpdateContainer(children, currentContainer, args); + reconcilorUpdateContainer(module, children, currentContainer, args); } return { @@ -72,10 +99,14 @@ export const CustomLayout: React.FC = (props: PropsWithChildr ); const removeContainer = useCallback((col: number, row: number) => { + const module = reconcilerModule.current; + if (!module) { + return; + } const key = `${col}-${row}`; if (container.current.has(key)) { const currentContainer = container.current.get(key); - reconcilor.updateContainer(null, currentContainer, null); + module.reconcilor.updateContainer(null, currentContainer, null); // group = currentContainer.containerInfo; currentContainer.containerInfo.delete(); container.current.delete(key); @@ -83,9 +114,14 @@ export const CustomLayout: React.FC = (props: PropsWithChildr }, []); const removeAllContainer = useCallback(() => { + const module = reconcilerModule.current; + if (!module) { + container.current.clear(); + return; + } container.current.forEach((value, key) => { const currentContainer = value; - reconcilor.updateContainer(null, currentContainer, null); + module.reconcilor.updateContainer(null, currentContainer, null); currentContainer.containerInfo.delete(); }); container.current.clear(); @@ -108,6 +144,9 @@ export const CustomLayout: React.FC = (props: PropsWithChildr // eslint-disable-next-line no-undef console.log('update props', props, table); + if (!reconcilerReady) { + return; + } table?.checkReactCustomLayout(); // init reactCustomLayout component table?.reactCustomLayout?.setReactRemoveAllGraphic(componentId, removeAllContainer, isHeaderCustomLayout); // set customLayout function @@ -129,6 +168,10 @@ export const CustomLayout: React.FC = (props: PropsWithChildr ); // update customLayout function // update all container container.current.forEach((value, key) => { + const module = reconcilerModule.current; + if (!module) { + return; + } const [col, row] = key.split('-').map(Number); // const width = table.getColWidth(col); // to be fixed: may be merge cell // const height = table.getRowHeight(row); // to be fixed: may be merge cell @@ -151,7 +194,7 @@ export const CustomLayout: React.FC = (props: PropsWithChildr }; // update element in container const group = currentContainer.containerInfo; - reconcilorUpdateContainer(children, currentContainer, args); + reconcilorUpdateContainer(module, children, currentContainer, args); // reconcilor.updateContainer(React.cloneElement(children, { ...args }), currentContainer, null); table.scenegraph.updateNextFrame(); }); @@ -161,8 +204,18 @@ export const CustomLayout: React.FC = (props: PropsWithChildr return null; }; -function reconcilorUpdateContainer(children: ReactElement, currentContainer: any, args: any) { +type ReconcilerModule = { + reconcilor: any; + createReconcilerContainer: ( + container: any, + identifierPrefix?: string, + reportError?: (type: ReconcilerErrorType, error: unknown) => void + ) => FiberRoot; +}; + +function reconcilorUpdateContainer(module: ReconcilerModule, children: ReactElement, currentContainer: any, args: any) { const element = React.cloneElement(children, { ...args }); + const { reconcilor } = module; const updateContainerSync = (reconcilor as any).updateContainerSync; if (typeof updateContainerSync === 'function') { updateContainerSync(element, currentContainer, null);