Skip to content

Commit 0bd567d

Browse files
authored
refactor: useContextSelector of all the context usage (#862)
* test: test driven * chore: all repalce with useContextSelector * test * chore: fix prettier remove import
1 parent 83bfad0 commit 0bd567d

21 files changed

+275
-118
lines changed

.fatherrc.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ export default {
44
runtimeHelpers: true,
55
preCommit: {
66
eslint: true,
7-
prettier: true,
7+
prettier: false,
88
},
99
};

docs/examples/simple.tsx

Lines changed: 39 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
/* eslint-disable no-console,func-names,react/no-multi-comp */
2-
import React from 'react';
32
import Table from 'rc-table';
3+
import React from 'react';
44
import '../../assets/index.less';
55

66
interface RecordType {
@@ -9,42 +9,50 @@ interface RecordType {
99
c?: string;
1010
}
1111

12-
const columns = [
13-
{ title: 'title1', dataIndex: 'a', key: 'a', width: 100 },
14-
{ id: '123', title: 'title2', dataIndex: 'b', key: 'b', width: 100, align: 'right' },
15-
{ title: 'title3', dataIndex: 'c', key: 'c', width: 200 },
16-
{
17-
title: 'Operations',
18-
dataIndex: '',
19-
key: 'd',
20-
render(_: any, record: RecordType) {
21-
return (
22-
<a
23-
onClick={e => {
24-
e.preventDefault();
25-
console.log('Operate on:', record);
26-
}}
27-
href="#"
28-
>
29-
Operations
30-
</a>
31-
);
32-
},
33-
},
34-
];
35-
3612
const data = [
3713
{ a: '123', key: '1' },
3814
{ a: 'cdd', b: 'edd', key: '2' },
3915
{ a: '1333', c: 'eee', d: 2, key: '3' },
4016
];
4117

42-
const Demo = () => (
43-
<div>
44-
<h2>simple table</h2>
45-
<Table<RecordType> columns={columns} data={data} />
46-
</div>
47-
);
18+
class Demo extends React.Component {
19+
constructor(props) {
20+
super(props);
21+
22+
this.state = {
23+
count: 0,
24+
};
25+
26+
this.columns = [
27+
{
28+
title: 'title1',
29+
dataIndex: 'a',
30+
render: this.renderColumn,
31+
},
32+
];
33+
}
34+
35+
renderColumn = () => {
36+
return this.state.count;
37+
};
38+
39+
render() {
40+
return (
41+
<>
42+
<button
43+
onClick={() => {
44+
this.setState({
45+
count: this.state.count + 1,
46+
});
47+
}}
48+
>
49+
Click {this.state.count} times
50+
</button>
51+
<Table<RecordType> columns={this.columns} data={data} />
52+
</>
53+
);
54+
}
55+
}
4856

4957
export default Demo;
5058
/* eslint-enable */

src/Body/BodyRow.tsx

Lines changed: 21 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,16 +1,17 @@
1-
import * as React from 'react';
21
import classNames from 'classnames';
2+
import * as React from 'react';
33
import Cell from '../Cell';
4-
import TableContext from '../context/TableContext';
54
import BodyContext from '../context/BodyContext';
6-
import { getColumnsKey } from '../utils/valueUtil';
5+
import TableContext from '../context/TableContext';
6+
import { useContextSelector } from '../ContextSelector';
77
import type {
88
ColumnType,
99
CustomizeComponent,
1010
GetComponentProps,
11-
Key,
1211
GetRowKey,
12+
Key,
1313
} from '../interface';
14+
import { getColumnsKey } from '../utils/valueUtil';
1415
import ExpandedRow from './ExpandedRow';
1516

1617
export interface BodyRowProps<RecordType> {
@@ -49,7 +50,10 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
4950
cellComponent,
5051
childrenColumnName,
5152
} = props;
52-
const { prefixCls, fixedInfoList } = React.useContext(TableContext);
53+
const { prefixCls, fixedInfoList } = useContextSelector(TableContext, [
54+
'prefixCls',
55+
'fixedInfoList',
56+
]);
5357
const {
5458
flattenColumns,
5559
expandableType,
@@ -61,7 +65,18 @@ function BodyRow<RecordType extends { children?: readonly RecordType[] }>(
6165
expandIcon,
6266
expandedRowRender,
6367
expandIconColumnIndex,
64-
} = React.useContext(BodyContext);
68+
} = useContextSelector(BodyContext, [
69+
'flattenColumns',
70+
'expandableType',
71+
'expandRowByClick',
72+
'onTriggerExpand',
73+
'rowClassName',
74+
'expandedRowClassName',
75+
'indentSize',
76+
'expandIcon',
77+
'expandedRowRender',
78+
'expandIconColumnIndex',
79+
]);
6580
const [expandRended, setExpandRended] = React.useState(false);
6681

6782
const expanded = expandedKeys && expandedKeys.has(props.recordKey);

src/Body/ExpandedRow.tsx

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import * as React from 'react';
2-
import type { CustomizeComponent } from '../interface';
32
import Cell from '../Cell';
4-
import TableContext from '../context/TableContext';
53
import ExpandedRowContext from '../context/ExpandedRowContext';
4+
import TableContext from '../context/TableContext';
5+
import { useContextSelector } from '../ContextSelector';
6+
import type { CustomizeComponent } from '../interface';
67

78
export interface ExpandedRowProps {
89
prefixCls: string;
@@ -25,9 +26,11 @@ function ExpandedRow({
2526
colSpan,
2627
isEmpty,
2728
}: ExpandedRowProps) {
28-
const { scrollbarSize } = React.useContext(TableContext);
29-
const { fixHeader, fixColumn, componentWidth, horizonScroll } =
30-
React.useContext(ExpandedRowContext);
29+
const scrollbarSize = useContextSelector(TableContext, 'scrollbarSize');
30+
const { fixHeader, fixColumn, componentWidth, horizonScroll } = useContextSelector(
31+
ExpandedRowContext,
32+
['fixHeader', 'fixColumn', 'componentWidth', 'horizonScroll'],
33+
);
3134

3235
// Cache render node
3336
return React.useMemo(() => {

src/Body/index.tsx

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,16 @@
11
import * as React from 'react';
2-
import TableContext from '../context/TableContext';
3-
import type { GetRowKey, Key, GetComponentProps } from '../interface';
4-
import ExpandedRow from './ExpandedRow';
52
import BodyContext from '../context/BodyContext';
6-
import { getColumnsKey } from '../utils/valueUtil';
7-
import ResizeContext from '../context/ResizeContext';
8-
import BodyRow from './BodyRow';
9-
import useFlattenRecords from '../hooks/useFlattenRecords';
103
import HoverContext from '../context/HoverContext';
114
import type { PerfRecord } from '../context/PerfContext';
125
import PerfContext from '../context/PerfContext';
6+
import ResizeContext from '../context/ResizeContext';
7+
import TableContext from '../context/TableContext';
8+
import { useContextSelector } from '../ContextSelector';
9+
import useFlattenRecords from '../hooks/useFlattenRecords';
10+
import type { GetComponentProps, GetRowKey, Key } from '../interface';
11+
import { getColumnsKey } from '../utils/valueUtil';
12+
import BodyRow from './BodyRow';
13+
import ExpandedRow from './ExpandedRow';
1314
import MeasureRow from './MeasureRow';
1415

1516
export interface BodyProps<RecordType> {
@@ -33,9 +34,12 @@ function Body<RecordType>({
3334
emptyNode,
3435
childrenColumnName,
3536
}: BodyProps<RecordType>) {
36-
const { onColumnResize } = React.useContext(ResizeContext);
37-
const { prefixCls, getComponent } = React.useContext(TableContext);
38-
const { flattenColumns } = React.useContext(BodyContext);
37+
const onColumnResize = useContextSelector(ResizeContext, 'onColumnResize');
38+
const { prefixCls, getComponent } = useContextSelector(TableContext, [
39+
'prefixCls',
40+
'getComponent',
41+
]);
42+
const flattenColumns = useContextSelector(BodyContext, 'flattenColumns');
3943

4044
const flattenData: { record: RecordType; indent: number; index: number }[] =
4145
useFlattenRecords<RecordType>(data, childrenColumnName, expandedKeys, getRowKey);

src/Cell/index.tsx

Lines changed: 15 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,25 @@
1-
import * as React from 'react';
21
import classNames from 'classnames';
3-
import shallowEqual from 'shallowequal';
42
import { supportRef } from 'rc-util/lib/ref';
3+
import warning from 'rc-util/lib/warning';
4+
import * as React from 'react';
5+
import shallowEqual from 'shallowequal';
6+
import BodyContext from '../context/BodyContext';
7+
import type { HoverContextProps } from '../context/HoverContext';
8+
import HoverContext from '../context/HoverContext';
9+
import PerfContext from '../context/PerfContext';
10+
import StickyContext from '../context/StickyContext';
11+
import { useContextSelector } from '../ContextSelector';
512
import type {
6-
DataIndex,
13+
AlignType,
14+
CellEllipsisType,
15+
CellType,
716
ColumnType,
8-
RenderedCell,
917
CustomizeComponent,
10-
CellType,
18+
DataIndex,
1119
DefaultRecordType,
12-
AlignType,
13-
CellEllipsisType,
20+
RenderedCell,
1421
} from '../interface';
1522
import { getPathValue, validateValue } from '../utils/valueUtil';
16-
import StickyContext from '../context/StickyContext';
17-
import HoverContext from '../context/HoverContext';
18-
import BodyContext from '../context/BodyContext';
19-
import type { HoverContextProps } from '../context/HoverContext';
20-
import warning from 'rc-util/lib/warning';
21-
import PerfContext from '../context/PerfContext';
22-
import { useContextSelector } from '../ContextSelector';
2323

2424
/** Check if cell is in hover range */
2525
function inHoverRange(cellStartRow: number, cellRowSpan: number, startRow: number, endRow: number) {
@@ -143,7 +143,7 @@ function Cell<RecordType extends DefaultRecordType>(
143143

144144
const perfRecord = React.useContext(PerfContext);
145145
const supportSticky = React.useContext(StickyContext);
146-
const { allColumnsFixedLeft } = React.useContext(BodyContext);
146+
const allColumnsFixedLeft = useContextSelector(BodyContext, 'allColumnsFixedLeft');
147147

148148
// ==================== Child Node ====================
149149
const [childNode, legacyCellProps] = React.useMemo<

src/ContextSelector/index.tsx

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import * as React from 'react';
2-
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
31
import useEvent from 'rc-util/lib/hooks/useEvent';
2+
import useLayoutEffect from 'rc-util/lib/hooks/useLayoutEffect';
3+
import * as React from 'react';
4+
import { unstable_batchedUpdates } from 'react-dom';
45
import shallowEqual from 'shallowequal';
56

67
export type Selector<T, O = T> = (value: T) => O;
@@ -24,8 +25,8 @@ export interface ReturnCreateContext<T> {
2425
Provider: React.ComponentType<ContextSelectorProviderProps<T>>;
2526
}
2627

27-
export function createContext<T>(): ReturnCreateContext<T> {
28-
const Context = React.createContext<Context<T>>(null as any);
28+
export function createContext<T>(defaultContext?: T): ReturnCreateContext<T> {
29+
const Context = React.createContext<Context<T>>(defaultContext as any);
2930

3031
const Provider = ({ value, children }: ContextSelectorProviderProps<T>) => {
3132
const valueRef = React.useRef(value);
@@ -37,8 +38,10 @@ export function createContext<T>(): ReturnCreateContext<T> {
3738
}));
3839

3940
useLayoutEffect(() => {
40-
context.listeners.forEach(listener => {
41-
listener(value);
41+
unstable_batchedUpdates(() => {
42+
context.listeners.forEach(listener => {
43+
listener(value);
44+
});
4245
});
4346
}, [value]);
4447

@@ -48,23 +51,55 @@ export function createContext<T>(): ReturnCreateContext<T> {
4851
return { Context, Provider };
4952
}
5053

51-
export function useContextSelector<T, O>(holder: ReturnCreateContext<T>, selector: Selector<T, O>) {
52-
const eventSelector = useEvent(selector);
54+
export function useContextSelector<T, O>(
55+
holder: ReturnCreateContext<T>,
56+
selector: Selector<T, O>,
57+
): O;
58+
export function useContextSelector<T, O extends Partial<T>>(
59+
holder: ReturnCreateContext<T>,
60+
selector: (keyof T)[],
61+
): O;
62+
export function useContextSelector<T, S extends keyof T>(
63+
holder: ReturnCreateContext<T>,
64+
selector: S,
65+
): T[S];
66+
67+
export function useContextSelector<T, O>(
68+
holder: ReturnCreateContext<T>,
69+
selector: Selector<T, any> | (keyof T)[] | keyof T,
70+
) {
71+
const eventSelector = useEvent<Selector<T, O>>(
72+
typeof selector === 'function'
73+
? selector
74+
: ctx => {
75+
if (!Array.isArray(selector)) {
76+
return ctx[selector];
77+
}
78+
79+
const obj = {} as O;
80+
selector.forEach(key => {
81+
(obj as any)[key] = ctx[key];
82+
});
83+
return obj;
84+
},
85+
);
5386
const context = React.useContext(holder?.Context);
5487
const { listeners, getValue } = context || {};
5588

56-
const [value, setValue] = React.useState(() => eventSelector(context ? getValue() : null));
89+
const valueRef = React.useRef<O>();
90+
valueRef.current = eventSelector(context ? getValue() : null);
91+
const [, forceUpdate] = React.useState({});
5792

5893
useLayoutEffect(() => {
5994
if (!context) {
6095
return;
6196
}
6297

6398
function trigger(nextValue: T) {
64-
setValue(prev => {
65-
const selectedValue = eventSelector(nextValue);
66-
return shallowEqual(prev, selectedValue) ? prev : selectedValue;
67-
});
99+
const nextSelectorValue = eventSelector(nextValue);
100+
if (!shallowEqual(valueRef.current, nextSelectorValue)) {
101+
forceUpdate({});
102+
}
68103
}
69104

70105
listeners.add(trigger);
@@ -74,5 +109,5 @@ export function useContextSelector<T, O>(holder: ReturnCreateContext<T>, selecto
74109
};
75110
}, [context]);
76111

77-
return value;
112+
return valueRef.current;
78113
}

0 commit comments

Comments
 (0)