diff --git a/packages/core/data/Table/TableRow.tsx b/packages/core/data/Table/TableRow.tsx index 03a4403b4..1155dd0fc 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' @@ -82,22 +82,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) => { @@ -117,6 +107,7 @@ function TableRow(props: Types.RowProps, ref: React.ForwardedRef } -export default 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 63cdcfa87..e7d38a878 100755 --- a/packages/core/data/Table/index.tsx +++ b/packages/core/data/Table/index.tsx @@ -3,13 +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, - useRef, - useState, -} from 'react' +import React, { useEffect, useImperativeHandle, useMemo, useRef, useState } from 'react' import { isBrowser, useSystem } from '@stage-ui/system' @@ -42,37 +36,65 @@ function Table(props: Types.Props, ref: React.ForwardedRef) { dataKey, } = props + const [reloadData, reload] = useState(false) + const [currentPage, setCurrentPage] = useState(1) const [primaryKey, setPrimaryKey] = useState('') + const [sort, setSort] = useState({ key: '', sort: 'ASC' }) - const [currentPage, setCurrentPage] = useState(1) - const [reloadData, reload] = useState(false) - const [sort, setSort] = useState({ - key: '', - sort: 'ASC', - }) - - const mapRowContext = (row: Types.Row): Types.TableRowContext => { - const isCellModify: Types.TableRowContext['isCellModify'] = {} - columns.forEach((column) => { - isCellModify[column.key] = false + const selectedSet = useMemo(() => { + return new Set((selected || []).map((s) => s[primaryKey] as string | number)) + }, [selected, primaryKey]) + + const baseRowCtx = useMemo(() => { + const rows = data.slice() + + if (!sort.key) return rows + + return rows.sort((a, b) => { + const aValue = a[sort.key] as string | number + const bValue = b[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, sort]) - let isSelected: Types.TableCellContext['isSelected'] = false - if (Array.isArray(selected) && primaryKey) { - isSelected = selected.findIndex((c) => c[primaryKey] === row[primaryKey]) >= 0 - } + const rowCtx = useMemo(() => { + return baseRowCtx.map((row: Types.Row): Types.TableRowContext => { + const isCellModify: Types.TableRowContext['isCellModify'] = {} + columns.forEach((column) => { + isCellModify[column.key] = false + }) - return { - row, - isExpand: false, - isVisible: true, - isCellModify, - isSelected, - setModifyState: {}, - } - } + let isSelected: Types.TableCellContext['isSelected'] = false + + if (primaryKey) { + isSelected = selectedSet.has(row[primaryKey] as string | number) + } - let rowCtx = data.slice().map(mapRowContext) + return { + row, + isExpand: false, + isVisible: true, + isCellModify, + isSelected, + setModifyState: {}, + } + }) + }, [baseRowCtx, selected, columns, primaryKey]) /** * Getting current data state @@ -82,26 +104,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 +173,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) { @@ -342,6 +336,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 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 +}