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

React集成D3时点击元素无法更新状态,如何避免依赖counter重渲染?

问题解决:React + D3 点击状态不更新(无需添加counter到useEffect依赖)

你的问题根源是闭包陷阱:useEffect只执行一次,绑定到D3元素上的handleClick是组件初始渲染时的版本,它捕获的counter永远是初始值0。哪怕后续counter更新,这个旧的handleClick里的counter还是0,所以每次点击都会执行setCounter(0+1),第一次能到1,但之后再点击就不会有变化了。

要解决这个问题,不需要把counter加入useEffect的依赖(避免D3元素重渲染),可以用以下两种方案:

方案1:使用函数式更新状态

React的setState支持传入函数,函数会接收当前最新的状态值,这样就不需要在useCallback里依赖counter,自然也不会因为counter变化导致handleClick重新创建,同时能拿到最新的状态:

修改后的组件代码:

import { useEffect, useState, useCallback } from "react";
import * as d3 from "d3";

export default function App() {
  const [counter, setCounter] = useState(0);

  // 用函数式更新,不需要依赖counter
  const handleClick = useCallback(() => {
    setCounter(prevCounter => prevCounter + 1);
  }, []); 

  useEffect(() => {
    const svg = d3.select("#svg");

    const redRect = svg
      .append("rect")
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", 300)
      .attr("height", 300)
      .attr("fill", "red")
      .attr("id", "red-rect");

    redRect.on("click", handleClick);
  }, [handleClick]); // handleClick依赖为空数组,useEffect仅执行一次

  // 可选:监听counter变化打印最新值
  useEffect(() => {
    console.log(counter);
  }, [counter]);

  return <svg id="svg" />;
}

方案2:用Ref保存最新的点击回调

如果需要在handleClick里直接访问最新的counter(而非仅用于更新状态),可以用useRef保存最新的回调,在useEffect里绑定一个固定的中间函数,每次点击时调用ref内的最新回调:

import { useEffect, useState, useRef } from "react";
import * as d3 from "d3";

export default function App() {
  const [counter, setCounter] = useState(0);
  const handleClickRef = useRef();

  // 每次渲染都更新ref的current为最新回调
  handleClickRef.current = () => {
    setCounter(counter + 1);
    console.log(counter);
  };

  useEffect(() => {
    const svg = d3.select("#svg");

    const redRect = svg
      .append("rect")
      .attr("x", 0)
      .attr("y", 0)
      .attr("width", 300)
      .attr("height", 300)
      .attr("fill", "red")
      .attr("id", "red-rect");

    // 绑定固定匿名函数,点击时调用ref内的最新回调
    redRect.on("click", () => {
      handleClickRef.current();
    });
  }, []); // 依赖为空数组,仅执行一次

  return <svg id="svg" />;
}

核心逻辑说明

  • 方案1中,handleClick的依赖为空数组,不会随counter变化重新创建,因此useEffect仅执行一次,D3元素不会重复渲染。
  • 方案2中,useEffect绑定的是固定匿名函数,依赖为空数组;handleClickRef的更新不会触发组件渲染,也不在useEffect依赖中,因此不会导致D3元素重渲染。

内容的提问来源于stack exchange,提问作者Wimal Weerawansa

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.26 22:53:08