diff --git a/src/hooks/useCache.ts b/src/hooks/useCache.ts index ae730e28..526b7c54 100644 --- a/src/hooks/useCache.ts +++ b/src/hooks/useCache.ts @@ -15,8 +15,8 @@ export default (values: LabeledValueType[]): [LabeledValueType[]] => { const valueLabelsCache = new Map(); 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); diff --git a/src/hooks/useCheckedKeys.ts b/src/hooks/useCheckedKeys.ts index d5d1e938..64a55ceb 100644 --- a/src/hooks/useCheckedKeys.ts +++ b/src/hooks/useCheckedKeys.ts @@ -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, -) => - 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; diff --git a/src/hooks/useFilterTreeData.ts b/src/hooks/useFilterTreeData.ts index 64373f10..2f764b30 100644 --- a/src/hooks/useFilterTreeData.ts +++ b/src/hooks/useFilterTreeData.ts @@ -2,22 +2,18 @@ import * as React from 'react'; import type { DefaultOptionType, InternalFieldName, TreeSelectProps } from '../TreeSelect'; import { fillLegacyProps } from '../utils/legacyUtil'; -type GetFuncType = T extends boolean ? never : T; -type FilterFn = GetFuncType; +type FilterFn = NonNullable; -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(() => { @@ -25,36 +21,30 @@ export default ( 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((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((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; diff --git a/src/hooks/useTreeData.ts b/src/hooks/useTreeData.ts index 26ffbebd..96ea50ad 100644 --- a/src/hooks/useTreeData.ts +++ b/src/hooks/useTreeData.ts @@ -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(); + 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[], @@ -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); diff --git a/src/utils/valueUtil.ts b/src/utils/valueUtil.ts index 220a0108..047e81e1 100644 --- a/src/utils/valueUtil.ts +++ b/src/utils/valueUtil.ts @@ -1,35 +1,29 @@ import type { DataNode, FieldNames, SafeKey } from '../interface'; import type { DefaultOptionType, InternalFieldName } from '../TreeSelect'; -export function toArray(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 = (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) { @@ -37,13 +31,11 @@ export function getAllKeys(treeData: DefaultOptionType[], fieldNames: InternalFi 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;