React中使用cloneElement处理按钮子元素时出现类型错误问题
尝试通过cloneElement添加按钮的子元素(div)时,触发如下错误:
Element type is invalid: expected a string (for built-in components) or a class/function (for composite components) but got: undefined. You likely forgot to export your component from the file it's defined in, or you might have mixed up default and named imports.
使用导入的组件(如<Test />)调用cloneElement时正常,但用event.currentTarget.children[0]就报错。相关代码如下:
const [CurrentIndex, setCurrentIndex] = useState(0); const [Symbols, setSymbols] = useState([{ symbols: [] }]); const appendToCurrent = (event) => { const clonedElement = cloneElement( event.currentTarget.children[0] ); setSymbols(prev => Object.assign([], prev, { [CurrentIndex]: { symbols: [...prev[CurrentIndex].symbols, clonedElement] } })) } <button onClick={event => appendToCurrent(event)}><div>A</div></button> <button onClick={event => appendToCurrent(event)}><div>B</div></button> {Symbols.map((field, i) => { return ( <div key={i}> <div onClick={() => setCurrentIndex(i)}> {field.symbols.map((symbol, i) => { return ( <div key={i}> {symbol} </div> ); })} </div> </div> ); })}
核心原因
event.currentTarget.children[0]拿到的是浏览器原生DOM节点,但React的cloneElement方法要求第一个参数必须是React虚拟元素(JSX编译后的对象),两者类型完全不匹配,直接传入就会报错。而你用<Test />这类组件能成功,是因为它们本身就是React虚拟元素,符合参数要求。
解决方法
不要直接从DOM里拿元素,而是提前把要添加的内容作为React元素存储,通过点击事件传递标识来获取对应的元素进行克隆。修改后的代码如下:
import { useState, cloneElement } from 'react'; const [CurrentIndex, setCurrentIndex] = useState(0); const [Symbols, setSymbols] = useState([{ symbols: [] }]); // 提前定义好需要的React元素 const availableSymbols = { A: <div>A</div>, B: <div>B</div> }; const appendToCurrent = (symbolKey) => { // 直接使用预定义的React元素克隆 const clonedElement = cloneElement(availableSymbols[symbolKey]); setSymbols(prev => { // 正确更新数组状态,避免Object.assign的潜在问题 const updatedSymbols = [...prev]; updatedSymbols[CurrentIndex] = { ...updatedSymbols[CurrentIndex], symbols: [...updatedSymbols[CurrentIndex].symbols, clonedElement] }; return updatedSymbols; }); }; // 点击时传递对应的标识,不再操作DOM <button onClick={() => appendToCurrent('A')}><div>A</div></button> <button onClick={() => appendToCurrent('B')}><div>B</div></button> {Symbols.map((field, i) => { return ( <div key={i}> <div onClick={() => setCurrentIndex(i)}> {field.symbols.map((symbol, idx) => ( <div key={idx}>{symbol}</div> ))} </div> </div> ); })}
额外提示
React的设计思路是通过状态管理虚拟DOM,尽量避免直接操作原生DOM。如果需要动态生成内容,优先通过状态存储数据或React元素,再根据状态渲染视图,这样能保证状态和视图的一致性,也符合React的最佳实践。
内容的提问来源于stack exchange,提问作者s.kuznetsov

