From 9007e8ecf41b6d7434db312398f3996018ef0ecf Mon Sep 17 00:00:00 2001 From: Jinke Li Date: Thu, 14 Nov 2024 11:48:15 +0800 Subject: [PATCH] =?UTF-8?q?fix:=20=E5=87=8F=E5=B0=91=E5=BC=80=E5=90=AF=20R?= =?UTF-8?q?eactDOM.unstable=5FbatchedUpdates=20=E5=90=8E=E7=9A=84=E9=87=8D?= =?UTF-8?q?=E6=B8=B2=E6=9F=93=E6=AC=A1=E6=95=B0=20(#2971)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: 减少开启 ReactDOM.unstable_batchedUpdates 后的重渲染次数 * test: 补充单测 * fix: 比较 dataCfg 使用 Object.is --- .../unit/sheet-type/pivot-sheet-spec.ts | 10 +- .../s2-core/src/common/interface/s2Options.ts | 2 +- .../s2-core/src/sheet-type/spread-sheet.ts | 6 +- packages/s2-core/src/utils/hide-columns.ts | 2 +- .../unit/hooks/useSpreadSheet-spec.ts | 98 ++++++++++++------- packages/s2-react/src/hooks/useSpreadSheet.ts | 19 +++- .../docs/api/basic-class/spreadsheet.zh.md | 2 +- .../docs/api/components/sheet-component.zh.md | 2 +- s2-site/docs/manual/migration-v2.zh.md | 13 ++- 9 files changed, 95 insertions(+), 59 deletions(-) diff --git a/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts b/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts index 743030fc75..80cfd60646 100644 --- a/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts +++ b/packages/s2-core/__tests__/unit/sheet-type/pivot-sheet-spec.ts @@ -773,7 +773,7 @@ describe('PivotSheet Tests', () => { test('should rebuild hidden columns detail by status', async () => { // 重新更新, 但是没有隐藏列信息 - await s2.render({ reloadData: false, reBuildHiddenColumnsDetail: true }); + await s2.render({ reloadData: false, rebuildHiddenColumnsDetail: true }); expect(mockHideColumnsByThunkGroup).toHaveBeenCalledTimes(0); @@ -781,16 +781,16 @@ describe('PivotSheet Tests', () => { null, ] as unknown as HiddenColumnsInfo[]); - // 重新更新, 有隐藏列信息, 但是 reBuildHiddenColumnsDetail 为 false + // 重新更新, 有隐藏列信息, 但是 rebuildHiddenColumnsDetail 为 false await s2.render({ reloadData: false, - reBuildHiddenColumnsDetail: false, + rebuildHiddenColumnsDetail: false, }); expect(mockHideColumnsByThunkGroup).toHaveBeenCalledTimes(0); - // 重新更新, 有隐藏列信息, 且 reBuildHiddenColumnsDetail 为 true - await s2.render({ reloadData: false, reBuildHiddenColumnsDetail: true }); + // 重新更新, 有隐藏列信息, 且 rebuildHiddenColumnsDetail 为 true + await s2.render({ reloadData: false, rebuildHiddenColumnsDetail: true }); expect(mockHideColumnsByThunkGroup).toHaveBeenCalledTimes(1); }); diff --git a/packages/s2-core/src/common/interface/s2Options.ts b/packages/s2-core/src/common/interface/s2Options.ts index d8ece16a92..8639a59e2e 100644 --- a/packages/s2-core/src/common/interface/s2Options.ts +++ b/packages/s2-core/src/common/interface/s2Options.ts @@ -386,5 +386,5 @@ export interface S2RenderOptions { /** * 是否重新生成列头隐藏信息 */ - reBuildHiddenColumnsDetail?: boolean; + rebuildHiddenColumnsDetail?: boolean; } diff --git a/packages/s2-core/src/sheet-type/spread-sheet.ts b/packages/s2-core/src/sheet-type/spread-sheet.ts index bd25959d66..3a38d9edb5 100644 --- a/packages/s2-core/src/sheet-type/spread-sheet.ts +++ b/packages/s2-core/src/sheet-type/spread-sheet.ts @@ -423,7 +423,7 @@ export abstract class SpreadSheet extends EE { const { reloadData = true, rebuildDataSet = false, - reBuildHiddenColumnsDetail = true, + rebuildHiddenColumnsDetail = true, } = options || {}; this.emit(S2Event.LAYOUT_BEFORE_RENDER); @@ -439,7 +439,7 @@ export abstract class SpreadSheet extends EE { this.buildFacet(); - if (reBuildHiddenColumnsDetail) { + if (rebuildHiddenColumnsDetail) { await this.initHiddenColumnsDetail(); } @@ -456,7 +456,7 @@ export abstract class SpreadSheet extends EE { s2.render({ reloadData: true; rebuildDataSet: true; - reBuildHiddenColumnsDetail: true; + rebuildHiddenColumnsDetail: true; }) */ public async render(options?: S2RenderOptions | boolean): Promise { diff --git a/packages/s2-core/src/utils/hide-columns.ts b/packages/s2-core/src/utils/hide-columns.ts index 2c71b16ea7..9c59de0128 100644 --- a/packages/s2-core/src/utils/hide-columns.ts +++ b/packages/s2-core/src/utils/hide-columns.ts @@ -131,7 +131,7 @@ export const hideColumns = async ( spreadsheet.store.set('hiddenColumnsDetail', hiddenColumnsDetail); await spreadsheet.render({ reloadData: false, - reBuildHiddenColumnsDetail: false, + rebuildHiddenColumnsDetail: false, }); }; diff --git a/packages/s2-react/__tests__/unit/hooks/useSpreadSheet-spec.ts b/packages/s2-react/__tests__/unit/hooks/useSpreadSheet-spec.ts index ba5d76e0ec..6ca3355795 100644 --- a/packages/s2-react/__tests__/unit/hooks/useSpreadSheet-spec.ts +++ b/packages/s2-react/__tests__/unit/hooks/useSpreadSheet-spec.ts @@ -23,6 +23,7 @@ describe('useSpreadSheet tests', () => { fields: S2DataConfig['fields'] = mockDataConfig.fields, ): SheetComponentProps => { return { + sheetType: 'pivot' as const, spreadsheet: () => new PivotSheet(getContainer(), mockDataConfig, s2Options as S2Options), options: s2Options, @@ -35,9 +36,7 @@ describe('useSpreadSheet tests', () => { test('should build spreadSheet', async () => { const props = getConfig(); - const { result } = renderHook(() => - useSpreadSheet({ ...props, sheetType: 'pivot' }), - ); + const { result } = renderHook(() => useSpreadSheet({ ...props })); await waitFor(() => { expect(result.current.s2Ref).toBeDefined(); @@ -47,7 +46,6 @@ describe('useSpreadSheet tests', () => { test('should cannot change table size when width or height updated and disable adaptive', async () => { const props = { ...getConfig(), - sheetType: 'pivot' as const, adaptive: false, }; const { result } = renderHook(() => useSpreadSheet(props)); @@ -126,7 +124,6 @@ describe('useSpreadSheet tests', () => { const props = { ...getConfig(), - sheetType: 'pivot' as const, onDestroy: onDestroyFromProps, }; const { result, unmount } = renderHook(() => useSpreadSheet(props)); @@ -160,7 +157,6 @@ describe('useSpreadSheet tests', () => { const props = { ...getConfig(), - sheetType: 'pivot' as const, onMounted, }; const { result } = renderHook(() => useSpreadSheet(props)); @@ -173,40 +169,67 @@ describe('useSpreadSheet tests', () => { }); }); - test('should call onUpdate and onUpdateAfterRender when sheet updated', async () => { - const onUpdate = jest.fn(); - const onUpdateAfterRender = jest.fn(); - - const props = { - ...getConfig(), - sheetType: 'pivot' as const, - onUpdate, - onUpdateAfterRender, - }; - const { rerender } = renderHook( - (innerProps) => useSpreadSheet(innerProps), - { - initialProps: props, + test.each([ + { + updatedProps: { + options: { width: 200 }, }, - ); - - await waitFor(() => { - expect(onUpdate).toHaveBeenCalledTimes(0); - expect(onUpdateAfterRender).toHaveBeenCalledTimes(0); - }); - - act(() => { - rerender({ ...props, options: { width: 200 } }); - }); - - await waitFor(() => { - expect(onUpdate).toHaveBeenCalledWith({ + updateOptions: { rebuildDataSet: false, reloadData: false, + }, + }, + { + updatedProps: { + themeCfg: { name: 'dark' }, + }, + updateOptions: { + rebuildDataSet: false, + reloadData: false, + }, + }, + { + updatedProps: { + dataCfg: { fields: ['test'] }, + }, + updateOptions: { + rebuildDataSet: false, + reloadData: true, + }, + }, + ])( + 'should call onUpdate and onUpdateAfterRender when sheet %o updated', + async ({ updatedProps, updateOptions }) => { + const onUpdate = jest.fn(); + const onUpdateAfterRender = jest.fn(); + + const props = { + ...getConfig(), + onUpdate, + onUpdateAfterRender, + }; + const { rerender } = renderHook( + (innerProps) => useSpreadSheet(innerProps), + { + initialProps: props, + }, + ); + + await waitFor(() => { + expect(onUpdate).toHaveBeenCalledTimes(0); + expect(onUpdateAfterRender).toHaveBeenCalledTimes(0); }); - expect(onUpdateAfterRender).toHaveBeenCalledTimes(1); - }); - }); + + act(() => { + rerender({ ...props, ...updatedProps }); + }); + + await waitFor(() => { + expect(onUpdate).toHaveBeenCalledWith(updateOptions); + expect(onUpdateAfterRender).toHaveBeenCalledTimes(1); + }); + }, + ); test('should use custom render mode by onUpdate', async () => { const onUpdate = jest.fn((options) => ({ ...options, reloadData: true })); @@ -214,7 +237,6 @@ describe('useSpreadSheet tests', () => { const props = { ...getConfig(), - sheetType: 'pivot' as const, onUpdate, onUpdateAfterRender, }; @@ -226,7 +248,7 @@ describe('useSpreadSheet tests', () => { ); act(() => { - rerender({ ...props, options: { width: 200 } }); + rerender({ ...props, options: { width: 300 } }); }); await waitFor(() => { diff --git a/packages/s2-react/src/hooks/useSpreadSheet.ts b/packages/s2-react/src/hooks/useSpreadSheet.ts index 2b37860b41..7426b975fa 100644 --- a/packages/s2-react/src/hooks/useSpreadSheet.ts +++ b/packages/s2-react/src/hooks/useSpreadSheet.ts @@ -1,7 +1,8 @@ +/* eslint-disable max-lines-per-function */ import type { S2DataConfig, S2Options, ThemeCfg } from '@antv/s2'; import { PivotSheet, SpreadSheet, TableSheet } from '@antv/s2'; import { useUpdate, useUpdateEffect } from 'ahooks'; -import { identity } from 'lodash'; +import { identity, isEqual } from 'lodash'; import React from 'react'; import type { SheetComponentOptions, SheetComponentProps } from '../components'; import { getSheetComponentOptions } from '../utils'; @@ -106,6 +107,7 @@ export function useSpreadSheet(props: SheetComponentProps) { updatePrevDepsRef.current = [dataCfg, options!, themeCfg!]; + let rerender = false; let reloadData = false; let rebuildDataSet = false; @@ -119,24 +121,33 @@ export function useSpreadSheet(props: SheetComponentProps) { } reloadData = true; + rerender = true; s2Ref.current?.setDataCfg(dataCfg); } - if (!Object.is(prevOptions, options)) { - if (!Object.is(prevOptions?.hierarchyType, options?.hierarchyType)) { + if (!isEqual(prevOptions, options)) { + if (prevOptions?.hierarchyType !== options?.hierarchyType) { rebuildDataSet = true; reloadData = true; s2Ref.current?.setDataCfg(dataCfg); } + rerender = true; s2Ref.current?.setOptions(options as S2Options); s2Ref.current?.changeSheetSize(options!.width, options!.height); } - if (!Object.is(prevThemeCfg, themeCfg)) { + if (!isEqual(prevThemeCfg, themeCfg)) { + rerender = true; s2Ref.current?.setThemeCfg(themeCfg); } + if (!rerender) { + setLoading(false); + + return; + } + /** * onUpdate 交出控制权 * 由传入方决定最终的 render 模式 diff --git a/s2-site/docs/api/basic-class/spreadsheet.zh.md b/s2-site/docs/api/basic-class/spreadsheet.zh.md index 66141907d3..561db92461 100644 --- a/s2-site/docs/api/basic-class/spreadsheet.zh.md +++ b/s2-site/docs/api/basic-class/spreadsheet.zh.md @@ -47,7 +47,7 @@ s2.isPivotMode() | setOptions | 更新表格配置 | (options: [S2Options](/docs/api/general/S2Options), reset?: boolean) => void | `reset` 参数需在 `@antv/s2^1.34.0`版本使用 | | resetDataCfg | 重置表格数据 | () => void | | | resetOptions | 重置表格配置 | () => void | | -| render | 重新渲染表格,如果 `reloadData` = true, 则会重新计算数据,`rebuildDataSet` = true, 重新构建数据集,`reBuildHiddenColumnsDetail` = true 重新构建隐藏列信息 | `(reloadData?: boolean \| { reloadData?: boolean, rebuildDataSet?: boolean; reBuildHiddenColumnsDetail?: boolean }) => Promise` | | +| render | 重新渲染表格,如果 `reloadData` = true, 则会重新计算数据,`rebuildDataSet` = true, 重新构建数据集,`rebuildHiddenColumnsDetail` = true 重新构建隐藏列信息 | `(reloadData?: boolean \| { reloadData?: boolean, rebuildDataSet?: boolean; rebuildHiddenColumnsDetail?: boolean }) => Promise` | | | destroy | 销毁表格 | `() => void` | | | setThemeCfg | 更新主题配置 (含主题 schema, 色板,主题名) | (themeCfg: [ThemeCfg](/docs/api/general/S2Theme/#themecfg)) => void | | | setTheme | 更新主题 (只包含主题 scheme) | (theme: [S2Theme](/docs/api/general/S2Theme/#s2theme)) => void | | diff --git a/s2-site/docs/api/components/sheet-component.zh.md b/s2-site/docs/api/components/sheet-component.zh.md index 5a236bd93a..81df202dfe 100644 --- a/s2-site/docs/api/components/sheet-component.zh.md +++ b/s2-site/docs/api/components/sheet-component.zh.md @@ -354,7 +354,7 @@ type SheetComponentOptions = S2Options< | -- | -- | -- | -- | --- | | reloadData | 是否重新加载数据 | `boolean` | | | | rebuildDataSet | 是否重新生成数据集 | `boolean` | | | -| reBuildHiddenColumnsDetail | 是否重新生成列头隐藏信息 | `boolean` | | | +| rebuildHiddenColumnsDetail | 是否重新生成列头隐藏信息 | `boolean` | | | diff --git a/s2-site/docs/manual/migration-v2.zh.md b/s2-site/docs/manual/migration-v2.zh.md index d566aa5944..a10a2d2f92 100644 --- a/s2-site/docs/manual/migration-v2.zh.md +++ b/s2-site/docs/manual/migration-v2.zh.md @@ -621,17 +621,20 @@ render 函数的参数从 `boolean` 扩展为 `boolean | object`, 当为 `boolea + s2.render({ reloadData: false }) // 等价于 s2.render(false) + s2.render({ + reloadData: false, -+ reBuildHiddenColumnsDetail: false, ++ rebuildHiddenColumnsDetail: false, + }); ``` `reBuildDataSet` 重命名为 `rebuildDataSet`: +`reBuildHiddenColumnsDetail` 重命名为 `rebuildHiddenColumnsDetail`: ```diff -+ s2.render({ -- reBuildDataSet: false, -+ rebuildDataSet: false, -+ }); +s2.render({ +- reBuildDataSet: false, ++ rebuildDataSet: false, +- reBuildHiddenColumnsDetail: false, ++ rebuildHiddenColumnsDetail: false, +}); ``` #### 小计总计配置参数变更