From 768626e8d140f6e29dfea96540b42ee26ae85a63 Mon Sep 17 00:00:00 2001 From: Sergey Vinogradov Date: Tue, 19 Nov 2024 18:31:47 +0400 Subject: [PATCH] use async rendering but defer it to microtask --- .../src/GridProEditColumn.tsx | 12 ++++--- packages/react-components/src/Grid.tsx | 25 +++++-------- packages/react-components/src/GridColumn.tsx | 12 +++++-- .../react-components/src/GridColumnGroup.tsx | 8 +++-- .../react-components/src/GridFilterColumn.tsx | 8 +++-- .../src/GridSelectionColumn.tsx | 12 +++++-- .../react-components/src/GridSortColumn.tsx | 8 +++-- .../react-components/src/GridTreeColumn.tsx | 8 +++-- .../src/renderers/useRenderer.ts | 35 +++++++++++-------- 9 files changed, 78 insertions(+), 50 deletions(-) diff --git a/packages/react-components-pro/src/GridProEditColumn.tsx b/packages/react-components-pro/src/GridProEditColumn.tsx index 19399683..c265a9df 100644 --- a/packages/react-components-pro/src/GridProEditColumn.tsx +++ b/packages/react-components-pro/src/GridProEditColumn.tsx @@ -134,12 +134,16 @@ function GridProEditColumn( ref: ForwardedRef>, ): ReactElement | null { const [editModePortals, editModeRenderer] = useModelRenderer(editColumnReactRenderer(props.editModeRenderer), { - renderSync: true, + renderMode: 'sync', + }); + const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, { + renderMode: 'microtask', + }); + const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, { + renderMode: 'microtask', }); - const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header); - const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer); const [bodyPortals, bodyRenderer] = useModelRenderer(editColumnReactRenderer(props.renderer ?? children), { - renderSync: true, + renderMode: 'sync', }); return ( diff --git a/packages/react-components/src/Grid.tsx b/packages/react-components/src/Grid.tsx index 8e63e9ca..6de33145 100644 --- a/packages/react-components/src/Grid.tsx +++ b/packages/react-components/src/Grid.tsx @@ -4,10 +4,8 @@ import { forwardRef, type ReactElement, type RefAttributes, - useEffect, useLayoutEffect, useRef, - useState, } from 'react'; import { Grid as _Grid, @@ -30,28 +28,21 @@ function Grid( props: GridProps, ref: ForwardedRef>, ): ReactElement | null { - const [portals, rowDetailsRenderer] = useModelRenderer(props.rowDetailsRenderer); + const [portals, rowDetailsRenderer] = useModelRenderer(props.rowDetailsRenderer, { + renderMode: 'microtask' + }); - const innerRef = useRef(null); + const innerRef = useRef(null); const finalRef = useMergedRefs(innerRef, ref); - const [pendingRecalculateColumnWidthsCall, setPendingRecalculateColumnWidthsCall] = useState(null); - useLayoutEffect(() => { - innerRef.current!._recalculateColumnWidths = function (...args) { - setPendingRecalculateColumnWidthsCall(args); + innerRef.current!.recalculateColumnWidths = function (...args) { + queueMicrotask(() => { + Object.getPrototypeOf(this).recalculateColumnWidths.call(this, ...args); + }); }; }, []); - useLayoutEffect(() => { - if (pendingRecalculateColumnWidthsCall) { - const gridElement = innerRef.current!; - const gridProto = Object.getPrototypeOf(gridElement); - gridProto._recalculateColumnWidths.call(gridElement, ...pendingRecalculateColumnWidthsCall); - setPendingRecalculateColumnWidthsCall(null); - } - }, [pendingRecalculateColumnWidthsCall]); - return ( <_Grid {...props} ref={finalRef} rowDetailsRenderer={rowDetailsRenderer}> {props.children} diff --git a/packages/react-components/src/GridColumn.tsx b/packages/react-components/src/GridColumn.tsx index 811ba89c..2b933518 100644 --- a/packages/react-components/src/GridColumn.tsx +++ b/packages/react-components/src/GridColumn.tsx @@ -54,9 +54,15 @@ function GridColumn( { children, footer, header, ...props }: GridColumnProps, ref: ForwardedRef>, ): ReactElement | null { - const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header); - const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer); - const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? children); + const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, { + renderMode: 'microtask', + }); + const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, { + renderMode: 'microtask', + }); + const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? children, { + renderMode: 'microtask', + }); return ( <_GridColumn diff --git a/packages/react-components/src/GridColumnGroup.tsx b/packages/react-components/src/GridColumnGroup.tsx index 3bd0eaa3..9a89f422 100644 --- a/packages/react-components/src/GridColumnGroup.tsx +++ b/packages/react-components/src/GridColumnGroup.tsx @@ -40,8 +40,12 @@ function GridColumnGroup( { children, footer, header, ...props }: GridColumnGroupProps, ref: ForwardedRef, ): ReactElement | null { - const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header); - const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer); + const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, { + renderMode: 'microtask', + }); + const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, { + renderMode: 'microtask', + }); return ( <_GridColumnGroup {...props} footerRenderer={footerRenderer} headerRenderer={headerRenderer} ref={ref}> diff --git a/packages/react-components/src/GridFilterColumn.tsx b/packages/react-components/src/GridFilterColumn.tsx index 741c3366..4a26ccbb 100644 --- a/packages/react-components/src/GridFilterColumn.tsx +++ b/packages/react-components/src/GridFilterColumn.tsx @@ -43,8 +43,12 @@ function GridFilterColumn( { footer, ...props }: GridFilterColumnProps, ref: ForwardedRef>, ): ReactElement | null { - const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer); - const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children); + const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, { + renderMode: 'microtask', + }); + const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children, { + renderMode: 'microtask', + }); return ( <_GridFilterColumn {...props} footerRenderer={footerRenderer} ref={ref} renderer={bodyRenderer}> diff --git a/packages/react-components/src/GridSelectionColumn.tsx b/packages/react-components/src/GridSelectionColumn.tsx index 223b6dcf..b0f23d5e 100644 --- a/packages/react-components/src/GridSelectionColumn.tsx +++ b/packages/react-components/src/GridSelectionColumn.tsx @@ -49,9 +49,15 @@ function GridSelectionColumn( { footer, header, ...props }: GridSelectionColumnProps, ref: ForwardedRef>, ): ReactElement | null { - const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header); - const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer); - const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children); + const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, { + renderMode: 'microtask', + }); + const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, { + renderMode: 'microtask', + }); + const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children, { + renderMode: 'microtask', + }); return ( <_GridSelectionColumn diff --git a/packages/react-components/src/GridSortColumn.tsx b/packages/react-components/src/GridSortColumn.tsx index d460c527..76c2bfb7 100644 --- a/packages/react-components/src/GridSortColumn.tsx +++ b/packages/react-components/src/GridSortColumn.tsx @@ -42,8 +42,12 @@ function GridSortColumn( { footer, ...props }: GridSortColumnProps, ref: ForwardedRef>, ): ReactElement | null { - const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer); - const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children); + const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, { + renderMode: 'microtask', + }); + const [bodyPortals, bodyRenderer] = useModelRenderer(props.renderer ?? props.children, { + renderMode: 'microtask', + }); return ( <_GridSortColumn {...props} footerRenderer={footerRenderer} ref={ref} renderer={bodyRenderer}> diff --git a/packages/react-components/src/GridTreeColumn.tsx b/packages/react-components/src/GridTreeColumn.tsx index fcd9cedf..2712b622 100644 --- a/packages/react-components/src/GridTreeColumn.tsx +++ b/packages/react-components/src/GridTreeColumn.tsx @@ -46,8 +46,12 @@ function GridTreeColumn( { footer, header, ...props }: GridTreeColumnProps, ref: ForwardedRef>, ): ReactElement | null { - const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header); - const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer); + const [headerPortals, headerRenderer] = useSimpleOrChildrenRenderer(props.headerRenderer, header, { + renderMode: 'microtask', + }); + const [footerPortals, footerRenderer] = useSimpleOrChildrenRenderer(props.footerRenderer, footer, { + renderMode: 'microtask', + }); return ( <_GridTreeColumn {...props} headerRenderer={headerRenderer} footerRenderer={footerRenderer} ref={ref}> diff --git a/packages/react-components/src/renderers/useRenderer.ts b/packages/react-components/src/renderers/useRenderer.ts index 79bb02b3..54404002 100644 --- a/packages/react-components/src/renderers/useRenderer.ts +++ b/packages/react-components/src/renderers/useRenderer.ts @@ -25,9 +25,25 @@ function rendererReducer( } export type RendererConfig = { - renderSync?: boolean; + renderMode?: 'default' | 'sync' | 'microtask'; }; +const renderQueue: Array<(...args: any[]) => any> = []; + +function flushMicrotask(callback: (...args: any[]) => any) { + renderQueue.push(callback); + + if (renderQueue.length === 1) { + queueMicrotask(() => { + flushSync(() => { + while (renderQueue.length) { + renderQueue.shift()!(); + } + }); + }); + } +} + export function useRenderer

( node: ReactNode, convert?: (props: Slice, 1>) => PropsWithChildren

, @@ -46,21 +62,10 @@ export function useRenderer

( const [map, update] = useReducer>(rendererReducer, initialState); const renderer = useCallback( ((...args: Parameters) => { - if (config?.renderSync) { - // The web components may request multiple synchronous renderer calls that - // would result in flushSync logging a warning (and actually executing the - // overlapping flushSync in microtask timing). Suppress the warning and allow - // the resulting asynchronicity. - const console = globalThis.console as any; - const error = console.error; - console.error = (message: string) => { - if (message.includes('flushSync')) { - return; - } - error(message); - }; + if (config?.renderMode === 'microtask') { + flushMicrotask(() => update(args)); + } else if (config?.renderMode === 'sync') { flushSync(() => update(args)); - console.error = error; } else { update(args); }