From a1cd6621ef8fd4c8e7ff46a6e7b1e2c3baec12af Mon Sep 17 00:00:00 2001 From: Alexandr Date: Mon, 7 Jul 2025 13:10:21 +0300 Subject: [PATCH 1/6] add useMemo to selectedSet & add useMemo to rowCtx --- packages/core/data/Table/index.tsx | 67 +++++++++++++++++------------- 1 file changed, 37 insertions(+), 30 deletions(-) diff --git a/packages/core/data/Table/index.tsx b/packages/core/data/Table/index.tsx index 63cdcfa87..b32942a3e 100755 --- a/packages/core/data/Table/index.tsx +++ b/packages/core/data/Table/index.tsx @@ -7,6 +7,7 @@ import React, { forwardRef, useEffect, useImperativeHandle, + useMemo, useRef, useState, } from 'react' @@ -51,6 +52,14 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { sort: 'ASC', }) + const selectedSet = useMemo( + () => + new Set( + (selected || []).map((s) => s[primaryKey] as string | number), + ), + [selected, primaryKey], + ) + const mapRowContext = (row: Types.Row): Types.TableRowContext => { const isCellModify: Types.TableRowContext['isCellModify'] = {} columns.forEach((column) => { @@ -58,8 +67,9 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { }) let isSelected: Types.TableCellContext['isSelected'] = false + if (Array.isArray(selected) && primaryKey) { - isSelected = selected.findIndex((c) => c[primaryKey] === row[primaryKey]) >= 0 + isSelected = selectedSet.has(row[primaryKey] as string | number) } return { @@ -72,7 +82,32 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { } } - let rowCtx = data.slice().map(mapRowContext) + const rowCtx = useMemo(() => { + const rows = data.slice().map(mapRowContext) + + if (!sort.key) return rows + + return rows.sort((a, b) => { + const aValue = a.row[sort.key] as string | number + const bValue = b.row[sort.key] as string | number + + if (sort.sort === 'ASC') { + if (typeof aValue === 'string') { + return aValue.localeCompare(bValue as string) + } + return aValue - (bValue as number) + } + + if (sort.sort === 'DESC') { + if (typeof bValue === 'string') { + return bValue.localeCompare(aValue as string) + } + return bValue - (aValue as number) + } + + return 0 + }) + }, [data, selected, columns, primaryKey, sort]) /** * Getting current data state @@ -82,26 +117,6 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { /** * Soring column by its settings */ - const columnSort = (value: Types.TableSortObject) => { - if (value.sort) { - rowCtx = rowCtx.sort((a, b) => { - if (value.sort === 'ASC') { - if (typeof a.row[value.key] === 'string') { - return (a.row[value.key] as string).localeCompare(b.row[value.key] as string) - } - return a.row[value.key] - b.row[value.key] - } - if (value.sort === 'DESC') { - if (typeof b.row[value.key] === 'string') { - return (b.row[value.key] as string).localeCompare(a.row[value.key] as string) - } - return b.row[value.key] - a.row[value.key] - } - return 0 - }) - } - } - const toggleSort = (value: Types.TableSortObject) => { return new Promise((resolve) => { const column = columns.find((currentColumn) => currentColumn.key === value.key) @@ -171,14 +186,6 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { } } - if (sort.key) { - columnSort(sort) - } else { - for (const column of columns) { - columnSort(column as Types.TableSortObject) - } - } - const setNeedDisplay = () => { let state = 1 for (const rowCtxItem of rowCtx) { From 2a07203a7bccf3ff67f686da5cf5955e123730df Mon Sep 17 00:00:00 2001 From: Alexandr Date: Mon, 7 Jul 2025 13:12:18 +0300 Subject: [PATCH 2/6] add memo export to table --- packages/core/data/Table/index.tsx | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/packages/core/data/Table/index.tsx b/packages/core/data/Table/index.tsx index b32942a3e..c9dadb321 100755 --- a/packages/core/data/Table/index.tsx +++ b/packages/core/data/Table/index.tsx @@ -3,14 +3,7 @@ /* eslint-disable guard-for-in */ /* eslint-disable no-restricted-syntax */ /* eslint-disable no-for-of-loops/no-for-of-loops */ -import React, { - forwardRef, - useEffect, - useImperativeHandle, - useMemo, - useRef, - useState, -} from 'react' +import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import { isBrowser, useSystem } from '@stage-ui/system' @@ -349,6 +342,10 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { ) } -export default forwardRef(Table) as ( - props: Types.Props & { ref?: React.ForwardedRef> }, -) => React.ReactElement +interface TableComponent { + ( + props: Types.Props & React.RefAttributes, + ): React.ReactElement +} + +export default React.memo(React.forwardRef(Table)) as TableComponent From b329fbe12a11ae873c0e91cfc7687165c60f7c55 Mon Sep 17 00:00:00 2001 From: Alexandr Date: Mon, 7 Jul 2025 13:51:32 +0300 Subject: [PATCH 3/6] divide useMemo from rowCtx --- packages/core/data/Table/index.tsx | 66 +++++++++++++++--------------- 1 file changed, 32 insertions(+), 34 deletions(-) diff --git a/packages/core/data/Table/index.tsx b/packages/core/data/Table/index.tsx index c9dadb321..4704e3bb9 100755 --- a/packages/core/data/Table/index.tsx +++ b/packages/core/data/Table/index.tsx @@ -36,14 +36,10 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { dataKey, } = props - const [primaryKey, setPrimaryKey] = useState('') - - const [currentPage, setCurrentPage] = useState(1) const [reloadData, reload] = useState(false) - const [sort, setSort] = useState({ - key: '', - sort: 'ASC', - }) + const [currentPage, setCurrentPage] = useState(1) + const [primaryKey, setPrimaryKey] = useState('') + const [sort, setSort] = useState({ key: '', sort: 'ASC' }) const selectedSet = useMemo( () => @@ -53,36 +49,14 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { [selected, primaryKey], ) - const mapRowContext = (row: Types.Row): Types.TableRowContext => { - const isCellModify: Types.TableRowContext['isCellModify'] = {} - columns.forEach((column) => { - isCellModify[column.key] = false - }) - - let isSelected: Types.TableCellContext['isSelected'] = false - - if (Array.isArray(selected) && primaryKey) { - isSelected = selectedSet.has(row[primaryKey] as string | number) - } - - return { - row, - isExpand: false, - isVisible: true, - isCellModify, - isSelected, - setModifyState: {}, - } - } - - const rowCtx = useMemo(() => { - const rows = data.slice().map(mapRowContext) + const baseRowCtx = useMemo(() => { + const rows = data.slice() if (!sort.key) return rows return rows.sort((a, b) => { - const aValue = a.row[sort.key] as string | number - const bValue = b.row[sort.key] as string | number + const aValue = a[sort.key] as string | number + const bValue = b[sort.key] as string | number if (sort.sort === 'ASC') { if (typeof aValue === 'string') { @@ -100,7 +74,31 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { return 0 }) - }, [data, selected, columns, primaryKey, sort]) + }, [data, sort]) + + const rowCtx = useMemo(() => { + return baseRowCtx.map((row: Types.Row): Types.TableRowContext => { + const isCellModify: Types.TableRowContext['isCellModify'] = {} + columns.forEach((column) => { + isCellModify[column.key] = false + }) + + let isSelected: Types.TableCellContext['isSelected'] = false + + if (primaryKey) { + isSelected = selectedSet.has(row[primaryKey] as string | number) + } + + return { + row, + isExpand: false, + isVisible: true, + isCellModify, + isSelected, + setModifyState: {}, + } + }) + }, [baseRowCtx, selected, columns, primaryKey]) /** * Getting current data state From 0ad2da568b87766847dfe6fdd4d4a666838d848c Mon Sep 17 00:00:00 2001 From: Alexandr Date: Mon, 7 Jul 2025 13:52:18 +0300 Subject: [PATCH 4/6] add fastMemo to TableRow --- packages/core/data/Table/TableRow.tsx | 3 +- packages/core/utils/fastMemo.ts | 8 +++++ .../core/utils/fastObjectShallowCompare.ts | 33 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) create mode 100644 packages/core/utils/fastMemo.ts create mode 100644 packages/core/utils/fastObjectShallowCompare.ts diff --git a/packages/core/data/Table/TableRow.tsx b/packages/core/data/Table/TableRow.tsx index 03a4403b4..263eb0189 100755 --- a/packages/core/data/Table/TableRow.tsx +++ b/packages/core/data/Table/TableRow.tsx @@ -4,6 +4,7 @@ import React, { forwardRef, useRef, useState } from 'react' import { isBrowser } from '@stage-ui/system' import Checkbox from '../../control/Checkbox' +import { fastMemo } from '../../utils/fastMemo' import TableCell from './TableCell' import Types from './types' @@ -154,4 +155,4 @@ function TableRow(props: Types.RowProps, ref: React.ForwardedRef } -export default forwardRef(TableRow) +export default fastMemo(forwardRef(TableRow)) diff --git a/packages/core/utils/fastMemo.ts b/packages/core/utils/fastMemo.ts new file mode 100644 index 000000000..06b9bd384 --- /dev/null +++ b/packages/core/utils/fastMemo.ts @@ -0,0 +1,8 @@ +/* eslint-disable @typescript-eslint/no-explicit-any */ +import * as React from 'react' + +import { fastObjectShallowCompare } from './fastObjectShallowCompare' + +export function fastMemo(component: T): T { + return React.memo(component as any, fastObjectShallowCompare) as unknown as T +} diff --git a/packages/core/utils/fastObjectShallowCompare.ts b/packages/core/utils/fastObjectShallowCompare.ts new file mode 100644 index 000000000..8e4c4a75f --- /dev/null +++ b/packages/core/utils/fastObjectShallowCompare.ts @@ -0,0 +1,33 @@ +/* eslint-disable guard-for-in */ +/* eslint-disable no-restricted-syntax */ +/* eslint-disable @typescript-eslint/no-explicit-any */ +const { is } = Object + +/** + * Fast shallow compare for objects. + * @returns true if objects are equal. + */ +export function fastObjectShallowCompare | null>( + a: T, + b: T, +) { + if (a === b) return true + + if (!(a instanceof Object) || !(b instanceof Object)) return false + + let aLength = 0 + let bLength = 0 + + for (const key in a) { + aLength += 1 + if (!is(a[key], b[key])) return false + if (!(key in b)) return false + } + + // eslint-disable-next-line @typescript-eslint/naming-convention + for (const _ in b) { + bLength += 1 + } + + return aLength === bLength +} From 78af53f4ced89fce30f882f644454f232c35a7ec Mon Sep 17 00:00:00 2001 From: Alexandr Date: Mon, 7 Jul 2025 15:15:35 +0300 Subject: [PATCH 5/6] remove timer for double click --- packages/core/data/Table/TableRow.tsx | 23 +++++++---------------- 1 file changed, 7 insertions(+), 16 deletions(-) diff --git a/packages/core/data/Table/TableRow.tsx b/packages/core/data/Table/TableRow.tsx index 263eb0189..f8b0d306c 100755 --- a/packages/core/data/Table/TableRow.tsx +++ b/packages/core/data/Table/TableRow.tsx @@ -1,5 +1,5 @@ /* eslint-disable no-bitwise */ -import React, { forwardRef, useRef, useState } from 'react' +import React, { forwardRef, useState } from 'react' import { isBrowser } from '@stage-ui/system' @@ -83,22 +83,12 @@ function TableRow(props: Types.RowProps, ref: React.ForwardedRef | null>(null) const onClick = (e: React.MouseEvent) => { - if (typeof onRowDoubleClick === 'function') { - if (rowClickTimer.current) { - clearTimeout(rowClickTimer.current) - rowClickTimer.current = null - onRowDoubleClick(e) - } else { - rowClickTimer.current = setTimeout(() => { - rowClickTimer.current = null - onRowClick?.(e) - }, 250) - } - } else { - onRowClick?.(e) - } + onRowClick?.(e) + } + + const onDoubleClick = (e: React.MouseEvent) => { + onRowDoubleClick?.(e) } const handleCheckboxClick = (e: React.MouseEvent) => { @@ -118,6 +108,7 @@ function TableRow(props: Types.RowProps, ref: React.ForwardedRef Date: Mon, 7 Jul 2025 17:37:39 +0300 Subject: [PATCH 6/6] fix use memo TableRow component to remove rerender all rows --- packages/core/data/Table/TableRow.tsx | 8 ++++++-- packages/core/data/Table/index.tsx | 10 +++------- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/packages/core/data/Table/TableRow.tsx b/packages/core/data/Table/TableRow.tsx index f8b0d306c..1155dd0fc 100755 --- a/packages/core/data/Table/TableRow.tsx +++ b/packages/core/data/Table/TableRow.tsx @@ -4,7 +4,6 @@ import React, { forwardRef, useState } from 'react' import { isBrowser } from '@stage-ui/system' import Checkbox from '../../control/Checkbox' -import { fastMemo } from '../../utils/fastMemo' import TableCell from './TableCell' import Types from './types' @@ -146,4 +145,9 @@ function TableRow(props: Types.RowProps, ref: React.ForwardedRef } -export default fastMemo(forwardRef(TableRow)) +export default React.memo(forwardRef(TableRow), (prev, next) => { + return ( + prev.rowCtxItem.row === next.rowCtxItem.row && + prev.rowCtxItem.isSelected === next.rowCtxItem.isSelected + ) +}) diff --git a/packages/core/data/Table/index.tsx b/packages/core/data/Table/index.tsx index 4704e3bb9..e7d38a878 100755 --- a/packages/core/data/Table/index.tsx +++ b/packages/core/data/Table/index.tsx @@ -41,13 +41,9 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { const [primaryKey, setPrimaryKey] = useState('') const [sort, setSort] = useState({ key: '', sort: 'ASC' }) - const selectedSet = useMemo( - () => - new Set( - (selected || []).map((s) => s[primaryKey] as string | number), - ), - [selected, primaryKey], - ) + const selectedSet = useMemo(() => { + return new Set((selected || []).map((s) => s[primaryKey] as string | number)) + }, [selected, primaryKey]) const baseRowCtx = useMemo(() => { const rows = data.slice()