使用React基础setState结合Excalidraw触发无限循环问题排查
React中使用Excalidraw(0.17.5)触发无限循环的问题分析
问题场景
尝试通过React状态追踪Excalidraw元素时,触发了无限循环报错,相关代码如下:
import { useState } from "react" import { Excalidraw } from "@excalidraw/excalidraw" import { type ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types" export function Graph() { const [exEls, setExEls] = useState<ExcalidrawElement[]>([]) return ( <div className="absolute top-0 w-screen h-screen"> <Excalidraw onChange={(els) => { setExEls(els.map((e) => e)) // `map` because it's `readonly` }} /> </div> ) }
报错信息:
Maximum update depth exceeded. This can happen when a component repeatedly calls setState inside componentWillUpdate or componentDidUpdate. React limits the number of nested updates to prevent infinite loops.
问题根源
这不是基础写法的问题,而是Excalidraw的onChange触发逻辑与React状态更新的联动导致的循环:
- 调用
setExEls更新状态时,Graph组件会重新渲染 - 组件重渲染会触发
Excalidraw组件的重新渲染,而Excalidraw内部会因自身状态或属性变化,再次触发onChange回调 - 回调中又调用
setExEls,形成渲染→触发onChange→更新状态→再渲染的无限循环
另外,你用els.map(e => e)只是浅拷贝数组,但Excalidraw返回的els数组和内部元素对象每次都是新的引用,React会判定状态始终在变化,进一步加剧循环触发频率。
解决方案
要打破循环,需避免状态更新直接触发组件重渲染引发的连锁反应,或添加条件判断仅在元素真正变化时更新状态:
方案1:用useRef存储元素(无需触发渲染场景)
如果不需要通过元素状态触发组件渲染,用useRef保存元素数组,更新ref不会触发组件重渲染:
import { useRef } from "react" import { Excalidraw } from "@excalidraw/excalidraw" import { type ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types" export function Graph() { const exElsRef = useRef<ExcalidrawElement[]>([]) return ( <div className="absolute top-0 w-screen h-screen"> <Excalidraw onChange={(els) => { exElsRef.current = [...els] // 浅拷贝保存最新元素 }} /> </div> ) }
方案2:添加状态更新的条件判断(需要触发渲染场景)
如果必须用useState触发渲染,可通过Excalidraw元素自带的id和version属性,对比元素是否真正变化,仅在有差异时更新状态:
import { useState, useCallback } from "react" import { Excalidraw } from "@excalidraw/excalidraw" import { type ExcalidrawElement } from "@excalidraw/excalidraw/types/element/types" export function Graph() { const [exEls, setExEls] = useState<ExcalidrawElement[]>([]) const handleChange = useCallback((els: ExcalidrawElement[]) => { // 对比元素数量、id或版本号判断是否有变化 const hasChanged = exEls.length !== els.length || exEls.some((el, idx) => el.id !== els[idx].id || el.version !== els[idx].version) if (hasChanged) { setExEls([...els]) } }, [exEls]) return ( <div className="absolute top-0 w-screen h-screen"> <Excalidraw onChange={handleChange} /> </div> ) }
补充说明
Excalidraw的onChange触发场景很多,包括元素拖拽、样式修改、内部状态同步等,直接绑定React状态容易触发循环。通过选择合适的存储方式(ref或带条件的state),完全可以正常追踪元素,并非无法实现。
内容的提问来源于stack exchange,提问作者psygo
相关产品推荐
相关产品推荐

