React中使用原生DOM操作遇appendChild错误,求代码修改方案
改造原生DOM代码适配React运行环境
原代码核心问题
- DOM节点获取时机错误:React组件的JSX还未渲染到真实DOM时,就执行了
document.getElementById('container'),此时无法获取到目标节点 - 语法错误:
circlesArr.forEach((15, i)是无效语法,应为circlesArr.forEach((cols, i) - 未处理内存泄漏:原生事件监听和定时器未在组件卸载时清理,可能引发内存问题
修改后的可运行代码
import { useRef, useEffect } from 'react'; export default function CircleGrid() { const containerRef = useRef(null); const circlesArr = useRef([]); const timerRefs = useRef([]); useEffect(() => { const container = containerRef.current; if (!container) return; // 初始化15x15的circle元素 for (let i = 0; i < 15; i++) { circlesArr.current[i] = []; for (let j = 0; j < 15; j++) { const circle = document.createElement('div'); circle.classList.add('circle'); container.appendChild(circle); circlesArr.current[i].push(circle); } } // 定义grow逻辑 function growCircles(i, j) { const circle = circlesArr.current[i]?.[j]; if (!circle || circle.classList.contains('grow')) return; circle.classList.add('grow'); // 存储定时器以便后续清理 const timer1 = setTimeout(() => { growCircles(i - 1, j); growCircles(i + 1, j); }, 100); const timer2 = setTimeout(() => { circle.classList.remove('grow'); }, 300); timerRefs.current.push(timer1, timer2); } // 绑定点击事件 circlesArr.current.forEach((cols, i) => { cols.forEach((circle, j) => { const handleClick = () => growCircles(i, j); circle.addEventListener('click', handleClick); // 存储事件处理函数以便卸载时移除 circle._clickHandler = handleClick; }); }); // 组件卸载时清理资源 return () => { // 清除所有定时器 timerRefs.current.forEach(timer => clearTimeout(timer)); // 移除所有事件监听并清理DOM circlesArr.current.forEach(cols => { cols.forEach(circle => { circle.removeEventListener('click', circle._clickHandler); circle.remove(); }); }); }; }, []); return <div ref={containerRef} className="container"></div>; }
更符合React范式的实现方案
上面的方案仍依赖原生DOM操作,更推荐用React的JSX和状态管理实现,避免直接操作DOM:
import { useState } from 'react'; export default function CircleGrid() { // 用二维数组存储每个circle的grow状态 const [growStates, setGrowStates] = useState( Array(15).fill().map(() => Array(15).fill(false)) ); const growCircles = (i, j) => { if (i < 0 || i >= 15 || j < 0 || j >= 15 || growStates[i][j]) return; // 更新当前circle的grow状态 setGrowStates(prev => { const newStates = [...prev]; newStates[i] = [...newStates[i]]; newStates[i][j] = true; return newStates; }); // 100ms后触发上下circle的grow setTimeout(() => { growCircles(i - 1, j); growCircles(i + 1, j); }, 100); // 300ms后取消grow状态 setTimeout(() => { setGrowStates(prev => { const newStates = [...prev]; newStates[i] = [...newStates[i]]; newStates[i][j] = false; return newStates; }); }, 300); }; return ( <div className="container"> {Array(15).fill().map((_, i) => ( <div key={i} className="row"> {Array(15).fill().map((_, j) => ( <div key={`${i}-${j}`} className={`circle ${growStates[i][j] ? 'grow' : ''}`} onClick={() => growCircles(i, j)} /> ))} </div> ))} </div> ); }
说明
- 第一种方案保留原代码的原生DOM操作逻辑,通过
useRef和useEffect确保DOM节点存在时执行操作,并添加资源清理逻辑 - 第二种方案完全使用React状态管理和JSX渲染,更符合React设计理念,避免直接操作DOM的潜在问题
内容的提问来源于stack exchange,提问作者User
相关产品推荐
相关产品推荐

