@ant-design/x
Version:
Craft AI-driven interfaces effortlessly
151 lines (144 loc) • 4.55 kB
JavaScript
import { useCallback, useEffect, useRef, useState } from 'react';
// 支持的输入类型集合
const SUPPORTED_INPUT_TYPES = new Set(['input', 'select', 'custom', 'content']);
/**
* 从 slot 配置中提取默认值构建 slotValues
*/
const buildSlotValues = slotConfig => slotConfig?.reduce((acc, node) => {
const {
key,
type
} = node;
if (!key) return acc;
const props = node.props || {};
const defaultValue = SUPPORTED_INPUT_TYPES.has(type) ? props.defaultValue : props.value ?? props.label;
acc[key] = defaultValue ?? '';
return acc;
}, {}) ?? {};
/**
* 将 slot 配置数组转换为 Map 结构便于查找
*/
const buildSlotConfigMap = (slotConfig, slotConfigMap) => {
slotConfig?.forEach(node => {
if (node.key) slotConfigMap.set(node.key, node);
});
};
/**
* 根据目标节点和配置映射获取节点信息
*/
const getNodeInfoBySlotConfigMap = (targetNode, slotConfigMap) => {
if (!targetNode?.dataset) return null;
const {
dataset
} = targetNode;
return {
slotKey: dataset.slotKey,
placeholder: dataset.placeholder,
nodeType: dataset.nodeType,
skillKey: dataset.skillKey,
slotConfig: dataset.slotKey ? slotConfigMap.get(dataset.slotKey) : undefined,
targetNode
};
};
const useSlotConfigState = slotConfig => {
const [state, _setState] = useState({});
const stateRef = useRef(state);
const slotConfigMapRef = useRef(new Map());
// Track keys inserted at runtime via mergeSlotConfig so the slotConfig effect below
// can preserve them when the prop reference changes (e.g. parent re-renders with an
// inline `[]`); otherwise ref.insert(...) entries silently disappear from getValue().
//
// Grows monotonically until clear(); intentional, the residual cost is one string
// per ever-inserted key.
const runtimeKeysRef = useRef(new Set());
// 初始化 slotConfig
useEffect(() => {
if (!slotConfig) return;
slotConfigMapRef.current.forEach((_, key) => {
if (!runtimeKeysRef.current.has(key)) slotConfigMapRef.current.delete(key);
});
buildSlotConfigMap(slotConfig, slotConfigMapRef.current);
const newValues = buildSlotValues(slotConfig);
runtimeKeysRef.current.forEach(key => {
if (key in stateRef.current) newValues[key] = stateRef.current[key];
});
_setState(newValues);
stateRef.current = newValues;
}, [slotConfig]);
const setState = useCallback(newValue => {
const value = typeof newValue === 'function' ? newValue(stateRef.current) : newValue;
_setState(value);
stateRef.current = value;
}, []);
const mergeSlotConfig = useCallback(newSlotConfig => {
const newValues = buildSlotValues(newSlotConfig);
newSlotConfig.forEach(node => {
if (node.key) {
slotConfigMapRef.current.set(node.key, node);
runtimeKeysRef.current.add(node.key);
}
});
_setState(prev => ({
...prev,
...newValues
}));
stateRef.current = {
...stateRef.current,
...newValues
};
}, []);
const clear = useCallback(() => {
slotConfigMapRef.current.clear();
runtimeKeysRef.current.clear();
_setState({});
stateRef.current = {};
}, []);
const getNodeInfo = useCallback(targetNode => getNodeInfoBySlotConfigMap(targetNode, slotConfigMapRef.current), []);
const getNodeTextValue = node => {
const nodeType = node.nodeType;
if (nodeType === Node.TEXT_NODE) {
return node.textContent || '';
}
if (nodeType !== Node.ELEMENT_NODE) {
return '';
}
const element = node;
const nodeInfo = getNodeInfo(element);
// 无节点信息,直接返回文本内容
if (!nodeInfo) {
return element.innerText || '';
}
const {
slotKey,
skillKey,
nodeType: infoNodeType,
slotConfig
} = nodeInfo;
if (skillKey) {
return '';
}
// 缓存文本内容,避免重复获取
const textContent = element.innerText || '';
// 处理 slot 节点
if (slotKey) {
if (infoNodeType === 'nbsp') {
return ' ';
}
if (!slotConfig || slotConfig.type === 'content') {
return textContent;
}
const slotValue = stateRef.current[slotKey] ?? '';
return slotConfig.formatResult?.(slotValue) ?? slotValue;
}
return textContent;
};
return [slotConfigMapRef.current, {
getSlotValues: () => stateRef.current,
setSlotValues: setState,
mergeSlotConfig,
getNodeInfo,
getNodeTextValue,
clear
}];
};
export default useSlotConfigState;