You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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状态更新的联动导致的循环:

  1. 调用setExEls更新状态时,Graph组件会重新渲染
  2. 组件重渲染会触发Excalidraw组件的重新渲染,而Excalidraw内部会因自身状态或属性变化,再次触发onChange回调
  3. 回调中又调用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 19:47:35