Skip to content

Commit 7f0dbd5

Browse files
authored
chore: optimize multiple onChange performence(#22822) (#499)
1 parent fcd7af4 commit 7f0dbd5

File tree

5 files changed

+87
-66
lines changed

5 files changed

+87
-66
lines changed

src/generate.tsx

Lines changed: 25 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
INTERNAL_PROPS_MARK,
3131
SelectSource,
3232
CustomTagProps,
33+
FlattenOptionMapType,
3334
} from './interface/generator';
3435
import { OptionListProps, RefOptionListProps } from './OptionList';
3536
import { toInnerValue, toOuterValues, removeLastEnabledValue, getUUID } from './utils/commonUtil';
@@ -40,6 +41,7 @@ import useLayoutEffect from './hooks/useLayoutEffect';
4041
import { getSeparatedContent } from './utils/valueUtil';
4142
import useSelectTriggerControl from './hooks/useSelectTriggerControl';
4243
import useCacheDisplayValue from './hooks/useCacheDisplayValue';
44+
import useCacheOptions from './hooks/useCacheOptions';
4345

4446
const DEFAULT_OMIT_PROPS = [
4547
'removeIcon',
@@ -182,14 +184,17 @@ export interface GenerateConfig<OptionsType extends object[]> {
182184
/** Flatten nest options into raw option list */
183185
flattenOptions: (options: OptionsType, props: any) => FlattenOptionsType<OptionsType>;
184186
/** Convert single raw value into { label, value } format. Will be called by each value */
185-
getLabeledValue: GetLabeledValue<FlattenOptionsType<OptionsType>>;
187+
getLabeledValue: GetLabeledValue<FlattenOptionMapType<OptionsType[number]>>;
186188
filterOptions: FilterOptions<OptionsType>;
187189
findValueOption: (
188190
values: RawValueType[],
189-
options: FlattenOptionsType<OptionsType>,
191+
optionMap: FlattenOptionMapType<OptionsType[number]>,
190192
) => OptionsType;
191193
/** Check if a value is disabled */
192-
isValueDisabled: (value: RawValueType, options: FlattenOptionsType<OptionsType>) => boolean;
194+
isValueDisabled: (
195+
value: RawValueType,
196+
optionMap: FlattenOptionMapType<OptionsType[number]>,
197+
) => boolean;
193198
warningProps?: (props: any) => void;
194199
fillOptionsWithMissingValue?: (
195200
options: OptionsType,
@@ -419,6 +424,8 @@ export default function generateSelector<
419424
[mergedOptions],
420425
);
421426

427+
const optionMap = useCacheOptions(mergedRawValue, mergedFlattenOptions);
428+
422429
// Display options for OptionList
423430
const displayOptions = React.useMemo<OptionsType>(() => {
424431
if (!mergedSearchValue || !mergedShowSearch) {
@@ -455,15 +462,15 @@ export default function generateSelector<
455462
() =>
456463
mergedRawValue.map((val: RawValueType) => {
457464
const displayValue = getLabeledValue(val, {
458-
options: mergedFlattenOptions,
465+
optionMap,
459466
prevValue: baseValue,
460467
labelInValue: mergedLabelInValue,
461468
optionLabelProp: mergedOptionLabelProp,
462469
});
463470

464471
return {
465472
...displayValue,
466-
disabled: isValueDisabled(val, mergedFlattenOptions),
473+
disabled: isValueDisabled(val, optionMap),
467474
};
468475
}),
469476
[baseValue, mergedOptions],
@@ -473,13 +480,13 @@ export default function generateSelector<
473480
displayValues = useCacheDisplayValue(displayValues);
474481

475482
const triggerSelect = (newValue: RawValueType, isSelect: boolean, source: SelectSource) => {
476-
const outOption = findValueOption([newValue], mergedFlattenOptions)[0];
483+
const outOption = findValueOption([newValue], optionMap)[0];
477484

478485
if (!internalProps.skipTriggerSelect) {
479486
// Skip trigger `onSelect` or `onDeselect` if configured
480487
const selectValue = (mergedLabelInValue
481488
? getLabeledValue(newValue, {
482-
options: mergedFlattenOptions,
489+
optionMap,
483490
prevValue: baseValue,
484491
labelInValue: mergedLabelInValue,
485492
optionLabelProp: mergedOptionLabelProp,
@@ -508,18 +515,21 @@ export default function generateSelector<
508515
return;
509516
}
510517

511-
const outValues = toOuterValues<FlattenOptionsType<OptionsType>>(Array.from(newRawValues), {
512-
labelInValue: mergedLabelInValue,
513-
options: mergedFlattenOptions,
514-
getLabeledValue,
515-
prevValue: baseValue,
516-
optionLabelProp: mergedOptionLabelProp,
517-
});
518+
const outValues = toOuterValues<FlattenOptionMapType<OptionsType[number]>>(
519+
Array.from(newRawValues),
520+
{
521+
labelInValue: mergedLabelInValue,
522+
optionMap,
523+
getLabeledValue,
524+
prevValue: baseValue,
525+
optionLabelProp: mergedOptionLabelProp,
526+
},
527+
);
518528

519529
const outValue: ValueType = (isMultiple ? outValues : outValues[0]) as ValueType;
520530
// Skip trigger if prev & current value is both empty
521531
if (onChange && (mergedRawValue.length !== 0 || outValues.length !== 0)) {
522-
const outOptions = findValueOption(newRawValues, mergedFlattenOptions);
532+
const outOptions = findValueOption(newRawValues, optionMap);
523533

524534
onChange(outValue, isMultiple ? outOptions : outOptions[0]);
525535
}

src/hooks/useCacheOptions.ts

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
import * as React from 'react';
2+
import { RawValueType, FlattenOptionsType, Key } from '../interface/generator';
3+
4+
export default function useCacheOptions<
5+
OptionsType extends {
6+
value?: RawValueType;
7+
label?: React.ReactNode;
8+
key?: Key;
9+
disabled?: boolean;
10+
}[]
11+
>(
12+
values: RawValueType[],
13+
options: FlattenOptionsType<OptionsType>,
14+
): Map<RawValueType, OptionsType[number]> {
15+
const prevOptionMapRef = React.useRef<Map<RawValueType, OptionsType[number]>>(null);
16+
17+
const optionMap = React.useMemo(() => {
18+
const map: Map<RawValueType, OptionsType[number]> = new Map();
19+
options.forEach(item => {
20+
if (!item.group) {
21+
const { data } = item;
22+
// Check if match
23+
map.set(data.value, data);
24+
}
25+
});
26+
return map;
27+
}, [values, options]);
28+
29+
prevOptionMapRef.current = optionMap;
30+
31+
return optionMap;
32+
}

src/interface/generator.ts

Lines changed: 12 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -14,19 +14,13 @@ export interface LabelValueType {
1414
value?: RawValueType;
1515
label?: React.ReactNode;
1616
}
17-
export type DefaultValueType =
18-
| RawValueType
19-
| RawValueType[]
20-
| LabelValueType
21-
| LabelValueType[];
17+
export type DefaultValueType = RawValueType | RawValueType[] | LabelValueType | LabelValueType[];
2218

2319
export interface DisplayLabelValueType extends LabelValueType {
2420
disabled?: boolean;
2521
}
2622

27-
export type SingleType<MixType> = MixType extends (infer Single)[]
28-
? Single
29-
: MixType;
23+
export type SingleType<MixType> = MixType extends (infer Single)[] ? Single : MixType;
3024

3125
export type OnClear = () => void;
3226

@@ -39,10 +33,10 @@ export type CustomTagProps = {
3933
};
4034

4135
// ==================================== Generator ====================================
42-
export type GetLabeledValue<FOT extends FlattenOptionsType> = (
36+
export type GetLabeledValue<FOT extends FlattenOptionMapType> = (
4337
value: RawValueType,
4438
config: {
45-
options: FOT;
39+
optionMap: FOT;
4640
prevValue: DefaultValueType;
4741
labelInValue: boolean;
4842
optionLabelProp: string;
@@ -59,19 +53,12 @@ export type FilterOptions<OptionsType extends object[]> = (
5953
},
6054
) => OptionsType;
6155

62-
export type FilterFunc<OptionType> = (
63-
inputValue: string,
64-
option?: OptionType,
65-
) => boolean;
56+
export type FilterFunc<OptionType> = (inputValue: string, option?: OptionType) => boolean;
6657

6758
export declare function RefSelectFunc<OptionsType extends object[], ValueType>(
68-
Component: React.RefForwardingComponent<
69-
RefSelectProps,
70-
SelectProps<OptionsType, ValueType>
71-
>,
59+
Component: React.RefForwardingComponent<RefSelectProps, SelectProps<OptionsType, ValueType>>,
7260
): React.ForwardRefExoticComponent<
73-
React.PropsWithoutRef<SelectProps<OptionsType, ValueType>> &
74-
React.RefAttributes<RefSelectProps>
61+
React.PropsWithoutRef<SelectProps<OptionsType, ValueType>> & React.RefAttributes<RefSelectProps>
7562
>;
7663

7764
export type FlattenOptionsType<OptionsType extends object[] = object[]> = {
@@ -80,3 +67,8 @@ export type FlattenOptionsType<OptionsType extends object[] = object[]> = {
8067
/** Used for customize data */
8168
[name: string]: any; // eslint-disable-line @typescript-eslint/no-explicit-any
8269
}[];
70+
71+
export type FlattenOptionMapType<OptionType extends object = object> = Map<
72+
DefaultValueType,
73+
OptionType
74+
>;

src/utils/commonUtil.ts

Lines changed: 9 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {
33
GetLabeledValue,
44
LabelValueType,
55
DefaultValueType,
6-
FlattenOptionsType,
6+
FlattenOptionMapType,
77
} from '../interface/generator';
88

99
export function toArray<T>(value: T | T[]): T[] {
@@ -27,8 +27,8 @@ export function toInnerValue(
2727
const values = Array.isArray(value) ? value : [value];
2828

2929
if (labelInValue) {
30-
return (values as LabelValueType[]).map(
31-
({ key, value: val }: LabelValueType) => (val !== undefined ? val : key),
30+
return (values as LabelValueType[]).map(({ key, value: val }: LabelValueType) =>
31+
(val !== undefined ? val : key),
3232
);
3333
}
3434

@@ -38,19 +38,19 @@ export function toInnerValue(
3838
/**
3939
* Convert internal value into out event value
4040
*/
41-
export function toOuterValues<FOT extends FlattenOptionsType>(
41+
export function toOuterValues<FOT extends FlattenOptionMapType>(
4242
valueList: RawValueType[],
4343
{
4444
optionLabelProp,
4545
labelInValue,
4646
prevValue,
47-
options,
47+
optionMap,
4848
getLabeledValue,
4949
}: {
5050
optionLabelProp: string;
5151
labelInValue: boolean;
5252
getLabeledValue: GetLabeledValue<FOT>;
53-
options: FOT;
53+
optionMap: FOT;
5454
prevValue: DefaultValueType;
5555
},
5656
): RawValueType[] | LabelValueType[] {
@@ -59,7 +59,7 @@ export function toOuterValues<FOT extends FlattenOptionsType>(
5959
if (labelInValue) {
6060
values = values.map(val =>
6161
getLabeledValue(val, {
62-
options,
62+
optionMap,
6363
prevValue,
6464
labelInValue,
6565
optionLabelProp,
@@ -77,11 +77,7 @@ export function removeLastEnabledValue<
7777
const newValues = [...values];
7878

7979
let removeIndex: number;
80-
for (
81-
removeIndex = measureValues.length - 1;
82-
removeIndex >= 0;
83-
removeIndex -= 1
84-
) {
80+
for (removeIndex = measureValues.length - 1; removeIndex >= 0; removeIndex -= 1) {
8581
if (!measureValues[removeIndex].disabled) {
8682
break;
8783
}
@@ -101,9 +97,7 @@ export function removeLastEnabledValue<
10197
}
10298

10399
export const isClient =
104-
typeof window !== 'undefined' &&
105-
window.document &&
106-
window.document.documentElement;
100+
typeof window !== 'undefined' && window.document && window.document.documentElement;
107101

108102
/** Is client side and not jsdom */
109103
export const isBrowserClient = process.env.NODE_ENV !== 'test' && isClient;

src/utils/valueUtil.ts

Lines changed: 9 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -89,26 +89,16 @@ function injectPropsWithOption<T>(option: T): T {
8989

9090
export function findValueOption(
9191
values: RawValueType[],
92-
options: FlattenOptionData[],
92+
optionMap: Map<RawValueType, OptionData>,
9393
): OptionData[] {
94-
const optionMap: Map<RawValueType, OptionData> = new Map();
95-
96-
options.forEach(flattenItem => {
97-
if (!flattenItem.group) {
98-
const data = flattenItem.data as OptionData;
99-
// Check if match
100-
optionMap.set(data.value, data);
101-
}
102-
});
103-
10494
return values.map(val => injectPropsWithOption(optionMap.get(val)));
10595
}
10696

107-
export const getLabeledValue: GetLabeledValue<FlattenOptionData[]> = (
97+
export const getLabeledValue: GetLabeledValue<Map<RawValueType, OptionData>> = (
10898
value,
109-
{ options, prevValue, labelInValue, optionLabelProp },
99+
{ optionMap, prevValue, labelInValue, optionLabelProp },
110100
) => {
111-
const item = findValueOption([value], options)[0];
101+
const item = findValueOption([value], optionMap)[0];
112102
const result: LabelValueType = {
113103
value,
114104
};
@@ -245,8 +235,11 @@ export function getSeparatedContent(text: string, tokens: string[]): string[] {
245235
return match ? list : null;
246236
}
247237

248-
export function isValueDisabled(value: RawValueType, options: FlattenOptionData[]): boolean {
249-
const option = findValueOption([value], options)[0];
238+
export function isValueDisabled(
239+
value: RawValueType,
240+
optionMap: Map<RawValueType, OptionData>,
241+
): boolean {
242+
const option = findValueOption([value], optionMap)[0];
250243
return option.disabled;
251244
}
252245

0 commit comments

Comments
 (0)