Skip to content

Commit

Permalink
perf: tree & treeselect #5365
Browse files Browse the repository at this point in the history
  • Loading branch information
tangjinzhou committed Mar 20, 2022
1 parent 96f5081 commit 7127a5d
Show file tree
Hide file tree
Showing 15 changed files with 222 additions and 109 deletions.
11 changes: 9 additions & 2 deletions components/table/hooks/useSelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import type {
ExpandType,
GetPopupContainer,
} from '../interface';
import useMaxLevel from '../../vc-tree/useMaxLevel';

// TODO: warning if use ajax!!!

Expand Down Expand Up @@ -120,7 +121,7 @@ export default function useSelection<RecordType>(

const keyEntities = computed(() =>
mergedRowSelection.value.checkStrictly
? { keyEntities: null }
? null
: convertDataToEntities(configRef.data.value as unknown as DataNode[], {
externalGetKey: configRef.getRowKey.value as any,
childrenPropName: configRef.childrenColumnName.value,
Expand Down Expand Up @@ -155,7 +156,7 @@ export default function useSelection<RecordType>(
});
return map;
});

const { maxLevel, levelEntities } = useMaxLevel(keyEntities);
const isCheckboxDisabled: GetCheckDisabled<RecordType> = (r: RecordType) =>
!!checkboxPropsMap.value.get(configRef.getRowKey.value(r))?.disabled;

Expand All @@ -167,6 +168,8 @@ export default function useSelection<RecordType>(
mergedSelectedKeys.value,
true,
keyEntities.value,
maxLevel.value,
levelEntities.value,
isCheckboxDisabled as any,
);
return [checkedKeys || [], halfCheckedKeys];
Expand Down Expand Up @@ -571,6 +574,8 @@ export default function useSelection<RecordType>(
[...originCheckedKeys, key],
true,
keyEntities.value,
maxLevel.value,
levelEntities.value,
isCheckboxDisabled as any,
);
const { checkedKeys, halfCheckedKeys } = result;
Expand All @@ -584,6 +589,8 @@ export default function useSelection<RecordType>(
Array.from(tempKeySet),
{ checked: false, halfCheckedKeys },
keyEntities.value,
maxLevel.value,
levelEntities.value,
isCheckboxDisabled as any,
).checkedKeys;
}
Expand Down
21 changes: 19 additions & 2 deletions components/vc-cascader/Cascader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ import { useProvideCascader } from './context';
import OptionList from './OptionList';
import { BaseSelect } from '../vc-select';
import devWarning from '../vc-util/devWarning';
import useMaxLevel from '../vc-tree/useMaxLevel';

export interface ShowSearchType<OptionType extends BaseOptionType = DefaultOptionType> {
filter?: (inputValue: string, options: OptionType[], fieldNames: FieldNames) => boolean;
Expand Down Expand Up @@ -259,6 +260,8 @@ export default defineComponent({
ref<SingleValueType[]>([]),
ref<SingleValueType[]>([]),
];

const { maxLevel, levelEntities } = useMaxLevel(pathKeyEntities);
watchEffect(() => {
const [existValues, missingValues] = missingValuesInfo.value;

Expand All @@ -274,7 +277,13 @@ export default defineComponent({
const keyPathValues = toPathKeys(existValues);
const ketPathEntities = pathKeyEntities.value;

const { checkedKeys, halfCheckedKeys } = conductCheck(keyPathValues, true, ketPathEntities);
const { checkedKeys, halfCheckedKeys } = conductCheck(
keyPathValues,
true,
ketPathEntities,
maxLevel.value,
levelEntities.value,
);

// Convert key back to value cells
[checkedValues.value, halfCheckedValues.value, missingCheckedValues.value] = [
Expand Down Expand Up @@ -356,9 +365,17 @@ export default defineComponent({
nextRawCheckedKeys,
{ checked: false, halfCheckedKeys: halfCheckedPathKeys },
pathKeyEntities.value,
maxLevel.value,
levelEntities.value,
));
} else {
({ checkedKeys } = conductCheck(nextRawCheckedKeys, true, pathKeyEntities.value));
({ checkedKeys } = conductCheck(
nextRawCheckedKeys,
true,
pathKeyEntities.value,
maxLevel.value,
levelEntities.value,
));
}

// Roll up to parent level keys
Expand Down
6 changes: 4 additions & 2 deletions components/vc-select/OptionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import classNames from '../_util/classNames';
import pickAttrs from '../_util/pickAttrs';
import { isValidElement } from '../_util/props-util';
import createRef from '../_util/createRef';
import { computed, defineComponent, nextTick, reactive, watch } from 'vue';
import { computed, defineComponent, nextTick, reactive, toRaw, watch } from 'vue';
import List from '../vc-virtual-list';
import useMemo from '../_util/hooks/useMemo';
import { isPlatformMac } from './utils/platformUtil';
Expand Down Expand Up @@ -105,7 +105,9 @@ const OptionList = defineComponent({
() => {
if (!baseProps.multiple && baseProps.open && props.rawValues.size === 1) {
const value = Array.from(props.rawValues)[0];
const index = memoFlattenOptions.value.findIndex(({ data }) => data.value === value);
const index = toRaw(memoFlattenOptions.value).findIndex(
({ data }) => data.value === value,
);
if (index !== -1) {
setActive(index);
nextTick(() => {
Expand Down
27 changes: 14 additions & 13 deletions components/vc-select/hooks/useFilterOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import type {
} from '../Select';
import { injectPropsWithOption } from '../utils/valueUtil';
import type { Ref } from 'vue';
import { computed } from 'vue';
import { toRaw, computed } from 'vue';

function includes(test: any, search: string) {
return toArray(test).join('').toUpperCase().includes(search);
Expand All @@ -22,22 +22,24 @@ export default (
optionFilterProp?: Ref<string>,
) =>
computed(() => {
if (!searchValue.value || filterOption.value === false) {
const searchValueVal = searchValue.value;
const optionFilterPropValue = optionFilterProp?.value;
const filterOptionValue = filterOption?.value;
if (!searchValueVal || filterOptionValue === false) {
return options.value;
}

const { options: fieldOptions, label: fieldLabel, value: fieldValue } = fieldNames.value;
const filteredOptions: DefaultOptionType[] = [];

const customizeFilter = typeof filterOption.value === 'function';
const customizeFilter = typeof filterOptionValue === 'function';

const upperSearch = searchValue.value.toUpperCase();
const upperSearch = searchValueVal.toUpperCase();
const filterFunc = customizeFilter
? (filterOption.value as FilterFunc<BaseOptionType>)
? (filterOptionValue as FilterFunc<BaseOptionType>)
: (_: string, option: DefaultOptionType) => {
// Use provided `optionFilterProp`
if (optionFilterProp.value) {
return includes(option[optionFilterProp.value], upperSearch);
if (optionFilterPropValue) {
return includes(option[optionFilterPropValue], upperSearch);
}

// Auto select `label` or `value` by option type
Expand All @@ -53,17 +55,17 @@ export default (
? opt => injectPropsWithOption(opt)
: opt => opt;

options.value.forEach(item => {
toRaw(options.value).forEach(item => {
// Group should check child options
if (item[fieldOptions]) {
// Check group first
const matchGroup = filterFunc(searchValue.value, wrapOption(item));
const matchGroup = filterFunc(searchValueVal, wrapOption(item));
if (matchGroup) {
filteredOptions.push(item);
} else {
// Check option
const subOptions = item[fieldOptions].filter((subItem: DefaultOptionType) =>
filterFunc(searchValue.value, wrapOption(subItem)),
filterFunc(searchValueVal, wrapOption(subItem)),
);
if (subOptions.length) {
filteredOptions.push({
Expand All @@ -76,10 +78,9 @@ export default (
return;
}

if (filterFunc(searchValue.value, wrapOption(item))) {
if (filterFunc(searchValueVal, wrapOption(item))) {
filteredOptions.push(item);
}
});

return filteredOptions;
});
14 changes: 7 additions & 7 deletions components/vc-select/hooks/useOptions.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Ref } from 'vue';
import { shallowRef, watchEffect } from 'vue';
import { toRaw, shallowRef, watchEffect } from 'vue';
import type { FieldNames, RawValueType } from '../Select';
import { convertChildrenToData } from '../utils/legacyUtil';

Expand All @@ -16,7 +16,7 @@ export default function useOptions<OptionType>(
const valueOptions = shallowRef();
const labelOptions = shallowRef();
watchEffect(() => {
let newOptions = options.value;
let newOptions = toRaw(options.value);
const childrenAsData = !options.value;

if (childrenAsData) {
Expand All @@ -25,16 +25,16 @@ export default function useOptions<OptionType>(

const newValueOptions = new Map<RawValueType, OptionType>();
const newLabelOptions = new Map<any, OptionType>();

const fieldNamesValue = fieldNames.value;
function dig(optionList: OptionType[], isChildren = false) {
// for loop to speed up collection speed
for (let i = 0; i < optionList.length; i += 1) {
const option = optionList[i];
if (!option[fieldNames.value.options] || isChildren) {
newValueOptions.set(option[fieldNames.value.value], option);
newLabelOptions.set(option[fieldNames.value.label], option);
if (!option[fieldNamesValue.options] || isChildren) {
newValueOptions.set(option[fieldNamesValue.value], option);
newLabelOptions.set(option[fieldNamesValue.label], option);
} else {
dig(option[fieldNames.value.options], true);
dig(option[fieldNamesValue.options], true);
}
}
}
Expand Down
6 changes: 3 additions & 3 deletions components/vc-tree-select/OptionList.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { TreeDataNode, Key } from './interface';
import type { RefOptionListProps } from '../vc-select/OptionList';
import type { ScrollTo } from '../vc-virtual-list/List';
import { computed, defineComponent, nextTick, ref, shallowRef, watch } from 'vue';
import { computed, defineComponent, nextTick, ref, shallowRef, toRaw, watch } from 'vue';
import useMemo from '../_util/hooks/useMemo';
import type { EventDataNode } from '../tree';
import KeyCode from '../_util/KeyCode';
Expand Down Expand Up @@ -89,7 +89,7 @@ export default defineComponent({
() => baseProps.searchValue,
() => {
if (baseProps.searchValue) {
searchExpandedKeys.value = getAllKeys(context.treeData, context.fieldNames);
searchExpandedKeys.value = getAllKeys(toRaw(context.treeData), toRaw(context.fieldNames));
}
},
{
Expand All @@ -98,7 +98,7 @@ export default defineComponent({
);
const mergedExpandedKeys = computed(() => {
if (legacyContext.treeExpandedKeys) {
return [...legacyContext.treeExpandedKeys];
return toRaw(legacyContext.treeExpandedKeys).slice();
}
return baseProps.searchValue ? searchExpandedKeys.value : expandedKeys.value;
});
Expand Down
25 changes: 19 additions & 6 deletions components/vc-tree-select/TreeSelect.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import type { VueNode } from '../_util/type';
import { conductCheck } from '../vc-tree/utils/conductUtil';
import { warning } from '../vc-util/warning';
import { toReactive } from '../_util/toReactive';
import useMaxLevel from '../vc-tree/useMaxLevel';

export type OnInternalSelect = (value: RawValueType, info: { selected: boolean }) => void;

Expand Down Expand Up @@ -364,13 +365,15 @@ export default defineComponent({

// const [mergedValues] = useCache(rawLabeledValues);
const rawValues = computed(() => rawLabeledValues.value.map(item => item.value));

const { maxLevel, levelEntities } = useMaxLevel(keyEntities);
// Convert value to key. Will fill missed keys for conduct check.
const [rawCheckedValues, rawHalfCheckedValues] = useCheckedKeys(
rawLabeledValues,
rawHalfLabeledValues,
treeConduction,
keyEntities,
maxLevel,
levelEntities,
);

// Convert rawCheckedKeys to check strategy related values
Expand Down Expand Up @@ -504,7 +507,9 @@ export default defineComponent({
selectedKey: Key,
{ selected, source }: { selected: boolean; source: SelectSource },
) => {
const entity = keyEntities.value[selectedKey];
const keyEntitiesValue = toRaw(keyEntities.value);
const valueEntitiesValue = toRaw(valueEntities.value);
const entity = keyEntitiesValue[selectedKey];
const node = entity?.node;
const selectedValue = node?.[mergedFieldNames.value.value] ?? selectedKey;

Expand All @@ -521,24 +526,32 @@ export default defineComponent({
if (treeConduction.value) {
// Should keep missing values
const { missingRawValues, existRawValues } = splitRawValues(newRawValues);
const keyList = existRawValues.map(val => valueEntities.value.get(val).key);
const keyList = existRawValues.map(val => valueEntitiesValue.get(val).key);

// Conduction by selected or not
let checkedKeys: Key[];
if (selected) {
({ checkedKeys } = conductCheck(keyList, true, keyEntities.value));
({ checkedKeys } = conductCheck(
keyList,
true,
keyEntitiesValue,
maxLevel.value,
levelEntities.value,
));
} else {
({ checkedKeys } = conductCheck(
keyList,
{ checked: false, halfCheckedKeys: rawHalfCheckedValues.value },
keyEntities.value,
keyEntitiesValue,
maxLevel.value,
levelEntities.value,
));
}

// Fill back of keys
newRawValues = [
...missingRawValues,
...checkedKeys.map(key => keyEntities.value[key].node[mergedFieldNames.value.value]),
...checkedKeys.map(key => keyEntitiesValue[key].node[mergedFieldNames.value.value]),
];
}
triggerChange(newRawValues, { selected, triggerValue: selectedValue }, source || 'option');
Expand Down
4 changes: 2 additions & 2 deletions components/vc-tree-select/hooks/useCache.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { Ref } from 'vue';
import { computed, shallowRef } from 'vue';
import { toRaw, computed, shallowRef } from 'vue';
import type { LabeledValueType, RawValueType } from '../TreeSelect';

/**
Expand All @@ -15,7 +15,7 @@ export default (values: Ref<LabeledValueType[]>): [Ref<LabeledValueType[]>] => {
const { valueLabels } = cacheRef.value;
const valueLabelsCache = new Map<RawValueType, any>();

const filledValues = values.value.map(item => {
const filledValues = toRaw(values.value).map(item => {
const { value } = item;
const mergedLabel = item.label ?? valueLabels.get(value);

Expand Down
20 changes: 15 additions & 5 deletions components/vc-tree-select/hooks/useCheckedKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,36 @@ import type { Key } from '../../_util/type';
import type { DataEntity } from '../../vc-tree/interface';
import { conductCheck } from '../../vc-tree/utils/conductUtil';
import type { LabeledValueType, RawValueType } from '../TreeSelect';
import type { Ref } from 'vue';
import { shallowRef, watchEffect } from 'vue';
import type { Ref, ShallowRef } from 'vue';
import { toRaw, shallowRef, watchEffect } from 'vue';

export default (
rawLabeledValues: Ref<LabeledValueType[]>,
rawHalfCheckedValues: Ref<LabeledValueType[]>,
treeConduction: Ref<boolean>,
keyEntities: Ref<Record<Key, DataEntity>>,
maxLevel: Ref<number>,
levelEntities: ShallowRef<Map<number, Set<DataEntity>>>,
) => {
const newRawCheckedValues = shallowRef<RawValueType[]>([]);
const newRawHalfCheckedValues = shallowRef<RawValueType[]>([]);

watchEffect(() => {
let checkedKeys: RawValueType[] = rawLabeledValues.value.map(({ value }) => value);
let halfCheckedKeys: RawValueType[] = rawHalfCheckedValues.value.map(({ value }) => value);
let checkedKeys: RawValueType[] = toRaw(rawLabeledValues.value).map(({ value }) => value);
let halfCheckedKeys: RawValueType[] = toRaw(rawHalfCheckedValues.value).map(
({ value }) => value,
);

const missingValues = checkedKeys.filter(key => !keyEntities.value[key]);

if (treeConduction.value) {
({ checkedKeys, halfCheckedKeys } = conductCheck(checkedKeys, true, keyEntities.value));
({ checkedKeys, halfCheckedKeys } = conductCheck(
checkedKeys,
true,
keyEntities.value,
maxLevel.value,
levelEntities.value,
));
}
newRawCheckedValues.value = Array.from(new Set([...missingValues, ...checkedKeys]));
newRawHalfCheckedValues.value = halfCheckedKeys;
Expand Down
Loading

1 comment on commit 7127a5d

@xuchao996
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

mark

Please sign in to comment.