React动态渲染JSX元素点击事件无法正确更新currentSelections状态
按需加载内容树的节点选中状态异常问题
我是React新手,正在实现一个可增量加载的内容树:初始仅渲染单个节点,用户点击+按钮时调用API加载该节点的子节点(仅加载未加载过的)并添加为内部节点,以此实现按需加载,避免一次性加载数千条数据以提升性能。
节点展开功能运行正常,但双击节点的selectNode函数存在异常:
- 每次选中节点时,
currentSelections状态始终被重置为当前选中项,函数内访问currentSelections时始终为undefined,无法实现选中项的追加(预期效果:[内容]→[内容,媒体库]→[内容,媒体库,布局],实际效果:[内容]→[媒体库]→[布局]) - 特殊情况:选中节点后关闭模态框,再次打开时之前的选中值会保留,新点击的节点会被追加到
currentSelections中,但再次点击其他节点时,仅会保留初始选中项和最新选中项,无法继续追加更多项
相关代码
const [currentSelections, setCurrentSelections] = useState<any[]>(); const [contentMainRoot, setContentMainRoot] = useState<Root>(); const resetTree = () => { if (contentMainRoot) { contentMainRoot.render(<ul id={sitecoreRootId}></ul>); } }; useEffect(() => { const rootElem = document.getElementById(sitecoreRootId); if (!rootElem) return; setContentMainRoot(createRoot(rootElem)); resetTree(); }, [activeInstance]); const toggleNode = async (e: any) => { if (!activeInstance) return; if (!e.target.classList.contains('loaded')) { const id = e.target.parentElement.getAttribute('data-id'); const results = await GetItemChildren(activeInstance, id); const children = results.children; console.log(children); // append ul root let root = null; if (id + '-root' === sitecoreRootId && contentMainRoot) { root = contentMainRoot; } else { const rootElem = document.getElementById(id + '-root'); if (!rootElem) return; root = createRoot(rootElem); } const innerTree = getInnerBrowseTree(id, children); root?.render(innerTree); e.target.classList.add('loaded'); e.target.classList.add('open'); } else { if (e.target.classList.contains('open')) { e.target.classList.remove('open'); } else { e.target.classList.add('open'); } } }; const selectNode = (e: any) => { const id = e.target.parentElement .getAttribute('data-id') .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5'); const name = e.target.parentElement.getAttribute('data-name'); console.log(id); if (e.target.classList.contains('selected')) { e.target.classList.remove('selected'); // remove id } else { let selectedItem = { itemId: id, name: name }; let selectedItems: any[] = currentSelections === undefined ? [selectedItem] : currentSelections?.concat(selectedItem); setCurrentSelections(selectedItems); e.target.classList.add('selected'); } }; const getInnerBrowseTree = (id: string, children: any[]) => { return ( <ul id={id}> {children.map((child, index) => ( <li key={index} data-name={child.name} data-id={child.itemId}> {child.hasChildren && <a className="browse-expand" onClick={(e) => toggleNode(e)}></a>} <a className="sitecore-node" onDoubleClick={(e) => selectNode(e)}> {child.name} </a> <ul id={child.itemId + '-root'}></ul> </li> ))} </ul> ); }; return ( <> {browseContentOpen && ( <div id="content-tree" className="content-tree"> <div className="inner"> <div className="flex items-center gap-2 mt-4"> <ul> <li data-name="sitecore" data-id="{11111111-1111-1111-1111-111111111111}"> <a className="browse-expand" onClick={(e) => toggleNode(e)}></a> <a className="sitecore-node" onDoubleClick={(e) => selectNode(e)}> sitecore </a> <ul id={sitecoreRootId}></ul> </li> </ul> <ul> {currentSelections && currentSelections?.map((item, index) => ( <li data-id={item.itemId} data-name={item.name} key={index}> {item.name} </li> ))} </ul> <Button variant="ghost" size="sm" onClick={() => setBrowseContentOpen(false)}> Close </Button> </div> </div> </div> )} </> )
解决方案
1. 使用函数式更新获取最新状态
React状态更新是异步的,直接访问currentSelections会拿到旧值。改用setCurrentSelections的函数式更新,确保每次基于最新状态修改:
const selectNode = (e: any) => { const id = e.target.parentElement .getAttribute('data-id') .replace(/(.{8})(.{4})(.{4})(.{4})(.{12})/, '$1-$2-$3-$4-$5'); const name = e.target.parentElement.getAttribute('data-name'); if (e.target.classList.contains('selected')) { e.target.classList.remove('selected'); // 移除选中项也用函数式更新 setCurrentSelections(prev => prev?.filter(item => item.itemId !== id)); } else { const selectedItem = { itemId: id, name: name }; // 基于最新状态追加,同时避免重复添加 setCurrentSelections(prev => { if (!prev) return [selectedItem]; if (prev.some(item => item.itemId === id)) return prev; return [...prev, selectedItem]; }); e.target.classList.add('selected'); } };
2. 初始化状态为空数组而非undefined
原代码中currentSelections初始化为undefined,导致判断逻辑复杂且容易出错,改为初始化为空数组:
const [currentSelections, setCurrentSelections] = useState<any[]>([]);
3. 避免手动操作DOM渲染子树
原代码使用createRoot和root.render手动渲染子树,不符合React数据流模式,容易导致状态与DOM不同步。建议维护一个treeNodes状态存储已加载的节点数据,通过状态驱动渲染,替代手动DOM操作。
内容的提问来源于stack exchange,提问作者Erica Stockwell-Alpert
相关产品推荐
相关产品推荐

