From a1e295c83231d1e69264aa257d3649c390e9bd61 Mon Sep 17 00:00:00 2001 From: zombiej Date: Mon, 4 Jul 2022 16:52:13 +0800 Subject: [PATCH 1/8] chore: init --- src/context/RenderContext.tsx | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 src/context/RenderContext.tsx diff --git a/src/context/RenderContext.tsx b/src/context/RenderContext.tsx new file mode 100644 index 000000000..fb5bad4fe --- /dev/null +++ b/src/context/RenderContext.tsx @@ -0,0 +1,5 @@ +import * as React from 'react'; + +const RenderContext = React.createContext(null); + +export default RenderContext; From 21ff8130b669680093cbc59aff826e1b7d89f2e0 Mon Sep 17 00:00:00 2001 From: zombiej Date: Mon, 4 Jul 2022 17:03:19 +0800 Subject: [PATCH 2/8] chore: rm render --- src/Cell/index.tsx | 62 +++++++--------------------------------------- src/interface.ts | 11 +------- 2 files changed, 10 insertions(+), 63 deletions(-) diff --git a/src/Cell/index.tsx b/src/Cell/index.tsx index 6a98c135b..e73641de9 100644 --- a/src/Cell/index.tsx +++ b/src/Cell/index.tsx @@ -5,9 +5,7 @@ import { supportRef } from 'rc-util/lib/ref'; import type { DataIndex, ColumnType, - RenderedCell, CustomizeComponent, - CellType, DefaultRecordType, AlignType, CellEllipsisType, @@ -16,8 +14,6 @@ import { getPathValue, validateValue } from '../utils/valueUtil'; import StickyContext from '../context/StickyContext'; import HoverContext from '../context/HoverContext'; import type { HoverContextProps } from '../context/HoverContext'; -import warning from 'rc-util/lib/warning'; -import PerfContext from '../context/PerfContext'; /** Check if cell is in hover range */ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: number, endRow: number) { @@ -25,12 +21,6 @@ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: numbe return cellStartRow <= endRow && cellEndRow >= startRow; } -function isRenderCell( - data: React.ReactNode | RenderedCell, -): data is RenderedCell { - return data && typeof data === 'object' && !Array.isArray(data) && !React.isValidElement(data); -} - function isRefComponent(component: CustomizeComponent) { // String tag component also support ref if (typeof component === 'string') { @@ -138,53 +128,28 @@ function Cell( ): React.ReactElement { const cellPrefixCls = `${prefixCls}-cell`; - const perfRecord = React.useContext(PerfContext); const supportSticky = React.useContext(StickyContext); // ==================== Child Node ==================== - const [childNode, legacyCellProps] = React.useMemo< - [React.ReactNode, CellType] | [React.ReactNode] - >(() => { + const childNode = React.useMemo(() => { if (validateValue(children)) { - return [children]; + return children; } - const value = getPathValue | React.ReactNode, RecordType>( - record, - dataIndex, - ); + const value = getPathValue(record, dataIndex); // Customize render node - let returnChildNode = value; - let returnCellProps: CellType | undefined = undefined; - if (render) { - const renderData = render(value, record, renderIndex); - - if (isRenderCell(renderData)) { - if (process.env.NODE_ENV !== 'production') { - warning( - false, - '`columns.render` return cell props is deprecated with perf issue, please use `onCell` instead.', - ); - } - returnChildNode = renderData.children; - returnCellProps = renderData.props; - perfRecord.renderWithProps = true; - } else { - returnChildNode = renderData; - } + return render(value, record, renderIndex); } - return [returnChildNode, returnCellProps]; + return value; }, [ /* eslint-disable react-hooks/exhaustive-deps */ // Always re-render if `renderWithProps` - perfRecord.renderWithProps ? Math.random() : 0, /* eslint-enable */ children, dataIndex, - perfRecord, record, render, renderIndex, @@ -205,15 +170,8 @@ function Cell( mergedChildNode = {mergedChildNode}; } - const { - colSpan: cellColSpan, - rowSpan: cellRowSpan, - style: cellStyle, - className: cellClassName, - ...restCellProps - } = legacyCellProps || {}; - const mergedColSpan = (cellColSpan !== undefined ? cellColSpan : colSpan) ?? 1; - const mergedRowSpan = (cellRowSpan !== undefined ? cellRowSpan : rowSpan) ?? 1; + const mergedColSpan = colSpan ?? 1; + const mergedRowSpan = rowSpan ?? 1; if (mergedColSpan === 0 || mergedRowSpan === 0) { return null; @@ -268,7 +226,6 @@ function Cell( ref: React.Ref; } = { title, - ...restCellProps, ...additionalProps, colSpan: mergedColSpan !== 1 ? mergedColSpan : null, rowSpan: mergedRowSpan !== 1 ? mergedRowSpan : null, @@ -285,12 +242,11 @@ function Cell( [`${cellPrefixCls}-ellipsis`]: ellipsis, [`${cellPrefixCls}-with-append`]: appendNode, [`${cellPrefixCls}-fix-sticky`]: (isFixLeft || isFixRight) && isSticky && supportSticky, - [`${cellPrefixCls}-row-hover`]: !legacyCellProps && hovering, + [`${cellPrefixCls}-row-hover`]: hovering, }, additionalProps.className, - cellClassName, ), - style: { ...additionalProps.style, ...alignStyle, ...fixedStyle, ...cellStyle }, + style: { ...additionalProps.style, ...alignStyle, ...fixedStyle }, onMouseEnter, onMouseLeave, ref: isRefComponent(Component) ? ref : null, diff --git a/src/interface.ts b/src/interface.ts index a0d146537..45a9db5cb 100644 --- a/src/interface.ts +++ b/src/interface.ts @@ -48,11 +48,6 @@ export interface CellType { colEnd?: number; } -export interface RenderedCell { - props?: CellType; - children?: React.ReactNode; -} - export type DataIndex = string | number | readonly (string | number)[]; export type CellEllipsisType = { showTitle?: boolean } | boolean; @@ -76,11 +71,7 @@ export type AlignType = 'left' | 'center' | 'right'; export interface ColumnType extends ColumnSharedType { colSpan?: number; dataIndex?: DataIndex; - render?: ( - value: any, - record: RecordType, - index: number, - ) => React.ReactNode | RenderedCell; + render?: (value: any, record: RecordType, index: number) => React.ReactNode; shouldCellUpdate?: (record: RecordType, prevRecord: RecordType) => boolean; rowSpan?: number; width?: number | string; From 1921d72a78bc64e9c2cdabc45ff8d720f299b001 Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 5 Jul 2022 10:24:43 +0800 Subject: [PATCH 3/8] test: part onCell --- src/Table.tsx | 12 +++++++++++- tests/Table.spec.js | 24 +++++++----------------- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/src/Table.tsx b/src/Table.tsx index 51383a158..afe8491ba 100644 --- a/src/Table.tsx +++ b/src/Table.tsx @@ -78,6 +78,7 @@ import Summary from './Footer/Summary'; import StickyContext from './context/StickyContext'; import ExpandedRowContext from './context/ExpandedRowContext'; import { EXPAND_COLUMN } from './constant'; +import RenderContext from './context/RenderContext'; // Used for conditions cache const EMPTY_DATA = []; @@ -171,7 +172,7 @@ export interface TableProps sticky?: boolean | TableSticky; } -function Table(props: TableProps) { +function InternalTable(props: TableProps) { const { prefixCls, className, @@ -863,6 +864,15 @@ function Table(props: TableProps(props: TableProps) { + // Tell cell that update is from parent + return ( + + + + ); +} + Table.EXPAND_COLUMN = EXPAND_COLUMN; Table.Column = Column; diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 6bc5b80dc..2aba9677c 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -348,13 +348,10 @@ describe('Table.Basic', () => { const columns = [ { dataIndex: 'key', - render: text => ({ - props: { - style: { background: 'red' }, - className: 'customize-render', - 'data-light': 'bamboo', - }, - children: text, + onCell: () => ({ + style: { background: 'red' }, + className: 'customize-render', + 'data-light': 'bamboo', }), }, ]; @@ -377,17 +374,10 @@ describe('Table.Basic', () => { title: 'Last Name', dataIndex: 'lastName', key: 'lastName', - render: (text, record, index) => { - const obj = { - children: text, - props: {}, + onCell: (_, index) => { + return { + rowSpan: index === 0 ? 2 : 0, }; - if (index === 0) { - obj.props.rowSpan = 2; - } else { - obj.props.rowSpan = 0; - } - return obj; }, }, ]; From 36e8a3fd3dc33f69fe4e87de7e07bd1557eb92d6 Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 5 Jul 2022 11:04:39 +0800 Subject: [PATCH 4/8] test: rm legacy test --- src/Cell/index.tsx | 14 ++++++++++- tests/Hover.spec.tsx | 58 ++------------------------------------------ tests/Table.spec.js | 26 +++++--------------- 3 files changed, 21 insertions(+), 77 deletions(-) diff --git a/src/Cell/index.tsx b/src/Cell/index.tsx index e73641de9..1931a01b6 100644 --- a/src/Cell/index.tsx +++ b/src/Cell/index.tsx @@ -14,6 +14,7 @@ import { getPathValue, validateValue } from '../utils/valueUtil'; import StickyContext from '../context/StickyContext'; import HoverContext from '../context/HoverContext'; import type { HoverContextProps } from '../context/HoverContext'; +import { warning } from 'rc-util/lib/warning'; /** Check if cell is in hover range */ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: number, endRow: number) { @@ -21,6 +22,10 @@ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: numbe return cellStartRow <= endRow && cellEndRow >= startRow; } +function isRenderCell(data: any) { + return data && typeof data === 'object' && !Array.isArray(data) && !React.isValidElement(data); +} + function isRefComponent(component: CustomizeComponent) { // String tag component also support ref if (typeof component === 'string') { @@ -140,7 +145,14 @@ function Cell( // Customize render node if (render) { - return render(value, record, renderIndex); + const renderNode = render(value, record, renderIndex); + if (process.env.NODE_ENV !== 'production' && isRenderCell(renderNode)) { + warning( + false, + '`column.render` do not support cell props any more, please use `onCell` instead.', + ); + } + return renderNode; } return value; diff --git a/tests/Hover.spec.tsx b/tests/Hover.spec.tsx index 651c9f91c..1d62ef460 100644 --- a/tests/Hover.spec.tsx +++ b/tests/Hover.spec.tsx @@ -42,7 +42,7 @@ describe('Table.Hover', () => { resetWarned(); const errorSpy = jest.spyOn(console, 'error').mockImplementation(() => {}); - const wrapper = mount( + mount( createTable({ columns: [ { @@ -66,23 +66,8 @@ describe('Table.Hover', () => { }), ); - // Merge row check - expect(wrapper.find('tbody td')).toHaveLength(3); - - // Hover 0-0 - wrapper.find('tbody td').at(0).simulate('mouseEnter'); - expect(wrapper.find('td.rc-table-cell-row-hover')).toHaveLength(2); - - // Hover 0-1 - wrapper.find('tbody td').at(1).simulate('mouseEnter'); - expect(wrapper.find('td.rc-table-cell-row-hover')).toHaveLength(1); - - // Mouse leave - wrapper.find('tbody td').at(1).simulate('mouseLeave'); - expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); - expect(errorSpy).toHaveBeenCalledWith( - 'Warning: `columns.render` return cell props is deprecated with perf issue, please use `onCell` instead.', + 'Warning: `column.render` do not support cell props any more, please use `onCell` instead.', ); errorSpy.mockRestore(); }); @@ -126,45 +111,6 @@ describe('Table.Hover', () => { }); describe('perf', () => { - it('legacy mode should render every time', () => { - let renderTimes = 0; - - const wrapper = mount( - createTable({ - columns: [ - { - render: () => { - renderTimes += 1; - return { - children: null, - }; - }, - }, - ], - }), - ); - - expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); - - // Hover 0-0 - renderTimes = 0; - wrapper.find('tbody td').at(0).simulate('mouseEnter'); - expect(wrapper.find('td.rc-table-cell-row-hover')).toHaveLength(1); - expect(renderTimes).toBe(1); - - // Hover 0-1 - renderTimes = 0; - wrapper.find('tbody td').at(1).simulate('mouseEnter'); - expect(wrapper.find('td.rc-table-cell-row-hover')).toHaveLength(1); - expect(renderTimes).toBe(2); - - // Mouse leave - renderTimes = 0; - wrapper.find('tbody td').at(1).simulate('mouseLeave'); - expect(wrapper.exists('.rc-table-cell-row-hover')).toBeFalsy(); - expect(renderTimes).toBe(1); - }); - it('perf mode to save render times', () => { let renderTimes = 0; diff --git a/tests/Table.spec.js b/tests/Table.spec.js index 2aba9677c..d886e58e1 100644 --- a/tests/Table.spec.js +++ b/tests/Table.spec.js @@ -308,32 +308,18 @@ describe('Table.Basic', () => { dataIndex: 'firstName', key: 'firstName', colSpan: 2, - render: (text, record, index) => { - const obj = { - children: text, - props: {}, - }; - if (index === 0) { - obj.props.colSpan = 2; - } - return obj; - }, + onCell: (_, index) => ({ + colSpan: index === 0 ? 2 : null, + }), }, { title: '', dataIndex: 'lastName', key: 'lastName', colSpan: 0, - render: (text, record, index) => { - const obj = { - children: text, - props: {}, - }; - if (index === 0) { - obj.props.colSpan = 0; - } - return obj; - }, + onCell: (_, index) => ({ + colSpan: index === 0 ? 0 : null, + }), }, ]; const localData = [ From fb99303243f9ed127d858cb0a295b0fc93c8c861 Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 5 Jul 2022 11:42:15 +0800 Subject: [PATCH 5/8] docs: add slow demo --- docs/demo/colspan-rowspan-legacy.md | 3 - docs/demo/hover-perf.md | 3 + docs/examples/colspan-rowspan-legacy.tsx | 150 ----------------------- docs/examples/hover-perf.tsx | 43 +++++++ 4 files changed, 46 insertions(+), 153 deletions(-) delete mode 100644 docs/demo/colspan-rowspan-legacy.md create mode 100644 docs/demo/hover-perf.md delete mode 100644 docs/examples/colspan-rowspan-legacy.tsx create mode 100644 docs/examples/hover-perf.tsx diff --git a/docs/demo/colspan-rowspan-legacy.md b/docs/demo/colspan-rowspan-legacy.md deleted file mode 100644 index 11809cef6..000000000 --- a/docs/demo/colspan-rowspan-legacy.md +++ /dev/null @@ -1,3 +0,0 @@ -## colspan-rowspan-legacy - - diff --git a/docs/demo/hover-perf.md b/docs/demo/hover-perf.md new file mode 100644 index 000000000..b15b020af --- /dev/null +++ b/docs/demo/hover-perf.md @@ -0,0 +1,3 @@ +## hover-perf + + diff --git a/docs/examples/colspan-rowspan-legacy.tsx b/docs/examples/colspan-rowspan-legacy.tsx deleted file mode 100644 index c67773d3c..000000000 --- a/docs/examples/colspan-rowspan-legacy.tsx +++ /dev/null @@ -1,150 +0,0 @@ -import React from 'react'; -import Table from 'rc-table'; -import '../../assets/index.less'; -import { ColumnsType, RenderedCell } from '@/interface'; - -interface RecordType { - a?: string; - b?: string; - c?: string; - d?: string; - e?: string; - key?: string; -} - -const columns: ColumnsType = [ - { - title: '手机号', - dataIndex: 'a', - colSpan: 2, - width: 100, - key: 'a', - render(o, row, index) { - const obj: RenderedCell = { - children: o, - props: {}, - }; - // 设置第一行为链接 - if (index === 0) { - obj.children = {o}; - } - // 第5行合并两列 - if (index === 4) { - obj.props.colSpan = 2; - } - - if (index === 5) { - obj.props.colSpan = 6; - } - return obj; - }, - }, - { - title: '电话', - dataIndex: 'b', - colSpan: 0, - width: 100, - key: 'b', - render(o, row, index) { - const obj: RenderedCell = { - children: o, - props: {}, - }; - // 列合并掉的表格设置colSpan=0,不会去渲染 - if (index === 4 || index === 5) { - obj.props.colSpan = 0; - } - return obj; - }, - }, - { - title: 'Name', - dataIndex: 'c', - width: 100, - key: 'c', - render(o, row, index) { - const obj: RenderedCell = { - children: o, - props: {}, - }; - - if (index === 5) { - obj.props.colSpan = 0; - } - return obj; - }, - }, - { - title: 'Address', - dataIndex: 'd', - width: 200, - key: 'd', - render(o, row, index) { - const obj: RenderedCell = { - children: o, - props: {}, - }; - if (index === 0) { - obj.props.rowSpan = 2; - } - if (index === 1 || index === 5) { - obj.props.rowSpan = 0; - } - - if (index === 5) { - obj.props.colSpan = 0; - } - - return obj; - }, - }, - { - title: 'Gender', - dataIndex: 'e', - width: 200, - key: 'e', - render(o, row, index) { - const obj: RenderedCell = { - children: o, - props: {}, - }; - if (index === 5) { - obj.props.colSpan = 0; - } - return obj; - }, - }, - { - title: 'Operations', - dataIndex: '', - key: 'f', - render(o, row, index) { - if (index === 5) { - return { - props: { - colSpan: 0, - }, - }; - } - return Operations; - }, - }, -]; - -const data: RecordType[] = [ - { a: '13812340987', b: '0571-12345678', c: '张三', d: '文一西路', e: 'Male', key: '1' }, - { a: '13812340986', b: '0571-98787658', c: '张夫人', d: '文一西路', e: 'Female', key: '2' }, - { a: '13812988888', b: '0571-099877', c: '李四', d: '文二西路', e: 'Male', key: '3' }, - { a: '1381200008888', b: '0571-099877', c: '王五', d: '文二西路', e: 'Male', key: '4' }, - { a: '0571-88888110', c: '李警官', d: '武林门', e: 'Male', key: '5' }, - { a: '资料统计完毕于xxxx年xxx月xxx日', key: '6' }, -]; - -const Demo = () => ( -
-

colSpan & rowSpan

- - -); - -export default Demo; diff --git a/docs/examples/hover-perf.tsx b/docs/examples/hover-perf.tsx new file mode 100644 index 000000000..2af7fe4c2 --- /dev/null +++ b/docs/examples/hover-perf.tsx @@ -0,0 +1,43 @@ +import React from 'react'; +import Table from 'rc-table'; +import '../../assets/index.less'; + +const CellExample = ({ data, count }) => { + console.log('rerender ' + Date.now()); + return <>{count + ' ' + data.index}; +}; + +const dataSource = Array.from({ length: 10000 }).map((_, index) => ({ index })); + +const ProblemTable = () => { + const columns = [ + { + title: 'Grouped by 10', + onCell: (_, index) => ({ rowSpan: index % 10 === 0 ? 10 : 0 }), + render: (_, record) => ( + + {record.index}-{record.index + 10} + + ), + }, + { + title: 'one', + render: (_, record) => , + }, + { + title: 'two', + render: (_, record) => , + }, + { + title: 'three', + render: (_, record) => , + }, + { + title: 'four', + render: (_, record) => , + }, + ]; + return
; +}; + +export default ProblemTable; From dcdb156c8502c615ef80bf6833fd255dad77d6ff Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 5 Jul 2022 15:34:00 +0800 Subject: [PATCH 6/8] chore: useContextSelector --- docs/examples/hover-perf.tsx | 3 +- src/Body/index.tsx | 9 ++--- src/Cell/index.tsx | 9 ++++- src/ContextSelector/index.tsx | 75 +++++++++++++++++++++++++++++++++++ src/context/HoverContext.tsx | 4 +- 5 files changed, 89 insertions(+), 11 deletions(-) create mode 100644 src/ContextSelector/index.tsx diff --git a/docs/examples/hover-perf.tsx b/docs/examples/hover-perf.tsx index 2af7fe4c2..ef739e2a5 100644 --- a/docs/examples/hover-perf.tsx +++ b/docs/examples/hover-perf.tsx @@ -7,7 +7,8 @@ const CellExample = ({ data, count }) => { return <>{count + ' ' + data.index}; }; -const dataSource = Array.from({ length: 10000 }).map((_, index) => ({ index })); +// const dataSource = Array.from({ length: 10000 }).map((_, index) => ({ index })); +const dataSource = Array.from({ length: 100 }).map((_, index) => ({ index, key: index })); const ProblemTable = () => { const columns = [ diff --git a/src/Body/index.tsx b/src/Body/index.tsx index 6fb7dcbbd..21f41139e 100644 --- a/src/Body/index.tsx +++ b/src/Body/index.tsx @@ -54,11 +54,6 @@ function Body({ setEndRow(end); }, []); - const hoverContext = React.useMemo( - () => ({ startRow, endRow, onHover }), - [onHover, startRow, endRow], - ); - // ====================== Render ====================== const bodyNode = React.useMemo(() => { const WrapperComponent = getComponent(['body', 'wrapper'], 'tbody'); @@ -141,7 +136,9 @@ function Body({ return ( - {bodyNode} + + {bodyNode} + ); } diff --git a/src/Cell/index.tsx b/src/Cell/index.tsx index 1931a01b6..c5c147bb4 100644 --- a/src/Cell/index.tsx +++ b/src/Cell/index.tsx @@ -15,6 +15,7 @@ import StickyContext from '../context/StickyContext'; import HoverContext from '../context/HoverContext'; import type { HoverContextProps } from '../context/HoverContext'; import { warning } from 'rc-util/lib/warning'; +import { useContextSelector } from '../ContextSelector'; /** Check if cell is in hover range */ function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: number, endRow: number) { @@ -295,7 +296,11 @@ const MemoCell = React.memo( /** Inject hover data here, we still wish MemoCell keep simple `shouldCellUpdate` logic */ const WrappedCell = React.forwardRef((props: CellProps, ref: React.Ref) => { - const { onHover, startRow, endRow } = React.useContext(HoverContext); + const { onHover, startRow, endRow } = useContextSelector(HoverContext, cxt => ({ + onHover: cxt?.onHover, + startRow: cxt?.startRow, + endRow: cxt?.endRow, + })); const { index, additionalProps = {}, colSpan, rowSpan } = props; const { colSpan: cellColSpan, rowSpan: cellRowSpan } = additionalProps; @@ -317,4 +322,4 @@ const WrappedCell = React.forwardRef((props: CellProps, ref: React.Ref }); WrappedCell.displayName = 'WrappedCell'; -export default WrappedCell; +export default React.memo(WrappedCell, () => true); diff --git a/src/ContextSelector/index.tsx b/src/ContextSelector/index.tsx new file mode 100644 index 000000000..140619b17 --- /dev/null +++ b/src/ContextSelector/index.tsx @@ -0,0 +1,75 @@ +import * as React from 'react'; +import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect'; +import shallowEqual from 'shallowequal'; + +export type Selector = (value: T) => O; + +export type Trigger = (value: T) => void; + +export type Listeners = Set>; + +export interface Context { + getValue: () => T; + listeners: Listeners; +} + +export interface ContextSelectorProviderProps { + value: T; + children?: React.ReactNode; +} + +export interface ReturnCreateContext { + Context: React.Context>; + Provider: React.ComponentType>; +} + +export function createContext(): ReturnCreateContext { + const Context = React.createContext>(null as any); + + const Provider = ({ value, children }: ContextSelectorProviderProps) => { + const valueRef = React.useRef(value); + valueRef.current = value; + + const [context] = React.useState>(() => ({ + getValue: () => valueRef.current, + listeners: new Set(), + })); + + useLayoutEffect(() => { + context.listeners.forEach(listener => { + listener(value); + }); + }, [value]); + + return {children}; + }; + + return { Context, Provider }; +} + +export function useContextSelector(holder: ReturnCreateContext, selector: Selector) { + const context = React.useContext(holder?.Context); + const { listeners, getValue } = context || {}; + const [value, setValue] = React.useState(() => selector(context ? getValue() : null)); + + React.useLayoutEffect(() => { + if (!context) { + return; + } + + function trigger(nextValue: T) { + setValue(prev => { + const selectedValue = selector(nextValue); + return shallowEqual(prev, selectedValue) ? prev : selectedValue; + }); + } + + listeners.add(trigger); + + return () => { + listeners.delete(trigger); + }; + }, [context]); + + return value; +} diff --git a/src/context/HoverContext.tsx b/src/context/HoverContext.tsx index c24703278..e5239ed55 100644 --- a/src/context/HoverContext.tsx +++ b/src/context/HoverContext.tsx @@ -1,4 +1,4 @@ -import * as React from 'react'; +import { createContext } from '../ContextSelector'; export interface HoverContextProps { startRow: number; @@ -6,6 +6,6 @@ export interface HoverContextProps { onHover: (start: number, end: number) => void; } -const HoverContext = React.createContext({} as any); +const HoverContext = createContext(); export default HoverContext; From 74d25a8fe31cae5a2b09c33b41bd47b32eec003e Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 5 Jul 2022 15:40:59 +0800 Subject: [PATCH 7/8] chore: do selector --- docs/examples/hover-perf.tsx | 4 ++-- src/Cell/index.tsx | 14 ++++++++------ src/ContextSelector/index.tsx | 7 +++++-- 3 files changed, 15 insertions(+), 10 deletions(-) diff --git a/docs/examples/hover-perf.tsx b/docs/examples/hover-perf.tsx index ef739e2a5..f760f215f 100644 --- a/docs/examples/hover-perf.tsx +++ b/docs/examples/hover-perf.tsx @@ -7,8 +7,8 @@ const CellExample = ({ data, count }) => { return <>{count + ' ' + data.index}; }; -// const dataSource = Array.from({ length: 10000 }).map((_, index) => ({ index })); -const dataSource = Array.from({ length: 100 }).map((_, index) => ({ index, key: index })); +const dataSource = Array.from({ length: 10000 }).map((_, index) => ({ index })); +// const dataSource = Array.from({ length: 100 }).map((_, index) => ({ index, key: index })); const ProblemTable = () => { const columns = [ diff --git a/src/Cell/index.tsx b/src/Cell/index.tsx index c5c147bb4..2e333e69c 100644 --- a/src/Cell/index.tsx +++ b/src/Cell/index.tsx @@ -296,18 +296,20 @@ const MemoCell = React.memo( /** Inject hover data here, we still wish MemoCell keep simple `shouldCellUpdate` logic */ const WrappedCell = React.forwardRef((props: CellProps, ref: React.Ref) => { - const { onHover, startRow, endRow } = useContextSelector(HoverContext, cxt => ({ - onHover: cxt?.onHover, - startRow: cxt?.startRow, - endRow: cxt?.endRow, - })); const { index, additionalProps = {}, colSpan, rowSpan } = props; const { colSpan: cellColSpan, rowSpan: cellRowSpan } = additionalProps; const mergedColSpan = colSpan ?? cellColSpan; const mergedRowSpan = rowSpan ?? cellRowSpan; - const hovering = inHoverRange(index, mergedRowSpan || 1, startRow, endRow); + const { onHover, hovering } = useContextSelector(HoverContext, cxt => { + const isHovering = inHoverRange(index, mergedRowSpan || 1, cxt?.startRow, cxt?.endRow); + + return { + onHover: cxt?.onHover, + hovering: isHovering, + }; + }); return ( = (value: T) => O; @@ -48,9 +49,11 @@ export function createContext(): ReturnCreateContext { } export function useContextSelector(holder: ReturnCreateContext, selector: Selector) { + const eventSelector = useEvent(selector); const context = React.useContext(holder?.Context); const { listeners, getValue } = context || {}; - const [value, setValue] = React.useState(() => selector(context ? getValue() : null)); + + const [value, setValue] = React.useState(() => eventSelector(context ? getValue() : null)); React.useLayoutEffect(() => { if (!context) { @@ -59,7 +62,7 @@ export function useContextSelector(holder: ReturnCreateContext, selecto function trigger(nextValue: T) { setValue(prev => { - const selectedValue = selector(nextValue); + const selectedValue = eventSelector(nextValue); return shallowEqual(prev, selectedValue) ? prev : selectedValue; }); } From 9186cd184947484e628c70b7f7aedc92487431be Mon Sep 17 00:00:00 2001 From: zombiej Date: Tue, 5 Jul 2022 15:43:25 +0800 Subject: [PATCH 8/8] chore: clean up --- docs/examples/hover-perf.tsx | 4 ++-- src/Cell/index.tsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/examples/hover-perf.tsx b/docs/examples/hover-perf.tsx index f760f215f..ef739e2a5 100644 --- a/docs/examples/hover-perf.tsx +++ b/docs/examples/hover-perf.tsx @@ -7,8 +7,8 @@ const CellExample = ({ data, count }) => { return <>{count + ' ' + data.index}; }; -const dataSource = Array.from({ length: 10000 }).map((_, index) => ({ index })); -// const dataSource = Array.from({ length: 100 }).map((_, index) => ({ index, key: index })); +// const dataSource = Array.from({ length: 10000 }).map((_, index) => ({ index })); +const dataSource = Array.from({ length: 100 }).map((_, index) => ({ index, key: index })); const ProblemTable = () => { const columns = [ diff --git a/src/Cell/index.tsx b/src/Cell/index.tsx index 2e333e69c..9b4a9a132 100644 --- a/src/Cell/index.tsx +++ b/src/Cell/index.tsx @@ -324,4 +324,4 @@ const WrappedCell = React.forwardRef((props: CellProps, ref: React.Ref }); WrappedCell.displayName = 'WrappedCell'; -export default React.memo(WrappedCell, () => true); +export default WrappedCell;