Skip to content

Commit

Permalink
refactor: refactor utils and hooks (#585)
Browse files Browse the repository at this point in the history
  • Loading branch information
afc163 authored Oct 15, 2024
1 parent 72d007a commit 63d472f
Show file tree
Hide file tree
Showing 5 changed files with 87 additions and 107 deletions.
4 changes: 2 additions & 2 deletions src/hooks/useCache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ export default (values: LabeledValueType[]): [LabeledValueType[]] => {
const valueLabelsCache = new Map<SafeKey, React.ReactNode>();

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

// Save in cache
valueLabelsCache.set(value, mergedLabel);
Expand Down
30 changes: 18 additions & 12 deletions src/hooks/useCheckedKeys.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,26 +3,32 @@ import type { DataEntity } from 'rc-tree/lib/interface';
import { conductCheck } from 'rc-tree/lib/utils/conductUtil';
import type { LabeledValueType, SafeKey } from '../interface';

export default (
const useCheckedKeys = (
rawLabeledValues: LabeledValueType[],
rawHalfCheckedValues: LabeledValueType[],
treeConduction: boolean,
keyEntities: Record<SafeKey, DataEntity>,
) =>
React.useMemo(() => {
let checkedKeys: SafeKey[] = rawLabeledValues.map(({ value }) => value);
let halfCheckedKeys: SafeKey[] = rawHalfCheckedValues.map(({ value }) => value);
) => {
return React.useMemo(() => {
const extractValues = (values: LabeledValueType[]): SafeKey[] =>
values.map(({ value }) => value);

const checkedKeys = extractValues(rawLabeledValues);
const halfCheckedKeys = extractValues(rawHalfCheckedValues);

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

let finalCheckedKeys = checkedKeys;
let finalHalfCheckedKeys = halfCheckedKeys;

if (treeConduction) {
({ checkedKeys, halfCheckedKeys } = conductCheck(checkedKeys, true, keyEntities));
const conductResult = conductCheck(checkedKeys, true, keyEntities);
finalCheckedKeys = conductResult.checkedKeys;
finalHalfCheckedKeys = conductResult.halfCheckedKeys;
}

return [
// Checked keys should fill with missing keys which should de-duplicated
Array.from(new Set([...missingValues, ...checkedKeys])),
// Half checked keys
halfCheckedKeys,
];
return [Array.from(new Set([...missingValues, ...finalCheckedKeys])), finalHalfCheckedKeys];
}, [rawLabeledValues, rawHalfCheckedValues, treeConduction, keyEntities]);
};

export default useCheckedKeys;
58 changes: 24 additions & 34 deletions src/hooks/useFilterTreeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,59 +2,49 @@ import * as React from 'react';
import type { DefaultOptionType, InternalFieldName, TreeSelectProps } from '../TreeSelect';
import { fillLegacyProps } from '../utils/legacyUtil';

type GetFuncType<T> = T extends boolean ? never : T;
type FilterFn = GetFuncType<TreeSelectProps['filterTreeNode']>;
type FilterFn = NonNullable<TreeSelectProps['filterTreeNode']>;

export default (
const useFilterTreeData = (
treeData: DefaultOptionType[],
searchValue: string,
{
treeNodeFilterProp,
filterTreeNode,
fieldNames,
}: {
options: {
fieldNames: InternalFieldName;
treeNodeFilterProp: string;
filterTreeNode: TreeSelectProps['filterTreeNode'];
},
) => {
const { fieldNames, treeNodeFilterProp, filterTreeNode } = options;
const { children: fieldChildren } = fieldNames;

return React.useMemo(() => {
if (!searchValue || filterTreeNode === false) {
return treeData;
}

let filterOptionFunc: FilterFn;
if (typeof filterTreeNode === 'function') {
filterOptionFunc = filterTreeNode;
} else {
const upperStr = searchValue.toUpperCase();
filterOptionFunc = (_, dataNode) => {
const value = dataNode[treeNodeFilterProp];

return String(value).toUpperCase().includes(upperStr);
};
}

function dig(list: DefaultOptionType[], keepAll: boolean = false) {
return list.reduce<DefaultOptionType[]>((total, dataNode) => {
const children = dataNode[fieldChildren];

const match = keepAll || filterOptionFunc(searchValue, fillLegacyProps(dataNode));
const childList = dig(children || [], match);

if (match || childList.length) {
total.push({
...dataNode,
const filterOptionFunc: FilterFn =
typeof filterTreeNode === 'function'
? filterTreeNode
: (_, dataNode) =>
String(dataNode[treeNodeFilterProp]).toUpperCase().includes(searchValue.toUpperCase());

const filterTreeNodes = (nodes: DefaultOptionType[], keepAll = false): DefaultOptionType[] =>
nodes.reduce<DefaultOptionType[]>((filtered, node) => {
const children = node[fieldChildren];
const isMatch = keepAll || filterOptionFunc(searchValue, fillLegacyProps(node));
const filteredChildren = filterTreeNodes(children || [], isMatch);

if (isMatch || filteredChildren.length) {
filtered.push({
...node,
isLeaf: undefined,
[fieldChildren]: childList,
[fieldChildren]: filteredChildren,
});
}
return total;
return filtered;
}, []);
}

return dig(treeData);
return filterTreeNodes(treeData);
}, [treeData, searchValue, fieldChildren, treeNodeFilterProp, filterTreeNode]);
};

export default useFilterTreeData;
60 changes: 26 additions & 34 deletions src/hooks/useTreeData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,45 +3,35 @@ import type { DataNode, SimpleModeConfig } from '../interface';
import { convertChildrenToData } from '../utils/legacyUtil';
import type { DefaultOptionType } from '../TreeSelect';

function parseSimpleTreeData(
treeData: DataNode[],
{ id, pId, rootPId }: SimpleModeConfig,
): DataNode[] {
const keyNodes = {};
const rootNodeList = [];

// Fill in the map
const nodeList = treeData.map(node => {
const clone = { ...node };
const key = clone[id];
keyNodes[key] = clone;
clone.key = clone.key || key;
return clone;
function buildTreeStructure(nodes: DataNode[], config: SimpleModeConfig): DataNode[] {
const { id, pId, rootPId } = config;
const nodeMap = new Map<string, DataNode>();
const rootNodes: DataNode[] = [];

nodes.forEach(node => {
const nodeKey = node[id];
const clonedNode = { ...node, key: node.key || nodeKey };
nodeMap.set(nodeKey, clonedNode);
});

// Connect tree
nodeList.forEach(node => {
nodeMap.forEach(node => {
const parentKey = node[pId];
const parent = keyNodes[parentKey];
const parent = nodeMap.get(parentKey);

// Fill parent
if (parent) {
parent.children = parent.children || [];
parent.children.push(node);
}

// Fill root tree node
if (parentKey === rootPId || (!parent && rootPId === null)) {
rootNodeList.push(node);
} else if (parentKey === rootPId || rootPId === null) {
rootNodes.push(node);
}
});

return rootNodeList;
return rootNodes;
}

/**
* Convert `treeData` or `children` into formatted `treeData`.
* Will not re-calculate if `treeData` or `children` not change.
* `treeData` `children` 转换为格式化的 `treeData`
* 如果 `treeData` `children` 没有变化,则不会重新计算。
*/
export default function useTreeData(
treeData: DataNode[],
Expand All @@ -50,14 +40,16 @@ export default function useTreeData(
): DefaultOptionType[] {
return React.useMemo(() => {
if (treeData) {
return simpleMode
? parseSimpleTreeData(treeData, {
id: 'id',
pId: 'pId',
rootPId: null,
...(simpleMode !== true ? simpleMode : {}),
})
: treeData;
if (simpleMode) {
const config: SimpleModeConfig = {
id: 'id',
pId: 'pId',
rootPId: null,
...(typeof simpleMode === 'object' ? simpleMode : {}),
};
return buildTreeStructure(treeData, config);
}
return treeData;
}

return convertChildrenToData(children);
Expand Down
42 changes: 17 additions & 25 deletions src/utils/valueUtil.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,41 @@
import type { DataNode, FieldNames, SafeKey } from '../interface';
import type { DefaultOptionType, InternalFieldName } from '../TreeSelect';

export function toArray<T>(value: T | T[]): T[] {
if (Array.isArray(value)) {
return value;
}
return value !== undefined ? [value] : [];
}

export function fillFieldNames(fieldNames?: FieldNames) {
const { label, value, children } = fieldNames || {};

const mergedValue = value || 'value';
export const toArray = <T>(value: T | T[]): T[] =>
Array.isArray(value) ? value : value !== undefined ? [value] : [];

export const fillFieldNames = (fieldNames?: FieldNames) => {
const { label, value, children } = fieldNames || {};
return {
_title: label ? [label] : ['title', 'label'],
value: mergedValue,
key: mergedValue,
value: value || 'value',
key: value || 'value',
children: children || 'children',
};
}
};

export function isCheckDisabled(node: DataNode) {
return !node || node.disabled || node.disableCheckbox || node.checkable === false;
}
export const isCheckDisabled = (node: DataNode): boolean =>
!node || node.disabled || node.disableCheckbox || node.checkable === false;

/** Loop fetch all the keys exist in the tree */
export function getAllKeys(treeData: DefaultOptionType[], fieldNames: InternalFieldName) {
export const getAllKeys = (
treeData: DefaultOptionType[],
fieldNames: InternalFieldName,
): SafeKey[] => {
const keys: SafeKey[] = [];

function dig(list: DefaultOptionType[]) {
const dig = (list: DefaultOptionType[]): void => {
list.forEach(item => {
const children = item[fieldNames.children];
if (children) {
keys.push(item[fieldNames.value]);
dig(children);
}
});
}
};

dig(treeData);

return keys;
}
};

export function isNil(val: any) {
return val === null || val === undefined;
}
export const isNil = (val: any): boolean => val === null || val === undefined;

0 comments on commit 63d472f

Please sign in to comment.