在useEffect钩子中运行动画是否安全?

8

我开始在我的代码中引入了一些React hooks,特别是useEffect,但我无法确定我做的是否安全。基本上,我在hook内部在DOM上运行动画,并且我想确保这不会破坏任何DOM快照,例如。

下面是一个例子,它是我从完整的示例中修改的,以便简明地说明发生了什么:

export function GrowingCircle(props) {

  const root = useRef(null);          // This is the root element we draw to

  // The actual rendering is done whenever the data changes
  useEffect(() => {

    const radius = props.width / 2;

    d3.select(root.current)
      .transition()
      .duration(1000)
      .attr("r", radius);

  }, [props.width]);

  return (
      <svg width={props.width} height="100%">
        <circle ref={root} cx="0" cy="0" r="0" fill="red" />
      </svg>
    );
}

我关心的部分是.transition()将在1秒内频繁更新DOM,我不确定这是否会影响react渲染?


作为后续问题(通常我们无法控制动画渲染,就像这个例子一样)。如果circle不再位于JSX中,会改变什么吗?

export function GrowingCircle(props) {

  const root = useRef(null);          // This is the root element we draw to

  // The actual rendering is done whenever the data changes
  useEffect(() => {

    const radius = props.width / 2;

    d3.select(root.current)
      .append("circle")
      .attr("cx", 0)
      .attr("cy", 0)
      .attr("fill", "red")
      .transition()
      .duration(1000)
      .attr("r", radius);

  }, [props.width]);

  return (
      <svg ref={root} width={props.width} height="100%">
      </svg>
    );
}
2个回答

2

我曾经遇到过和你相似的情况。简单回答一下,由于没有真正的D3-React实现,你需要自己划分命令式和声明式渲染的边界。然而,在你的两个例子中,你并没有违反任何规则。

在第二个例子中,你将控制权交给了D3进行完整的渲染,而React只是保留了对顶层svg元素的引用。而在第一个例子中,你只渲染了一个圆形,并且只使用D3来管理其转换。

然而,在第一个例子中,由于看起来在声明后没有改变DOM中的任何内容,React通常不会干涉。下面是第三个例子,实现了与你写的类似的效果:

export function GrowingCircle(props) {

  const root = useRef(null);          // This is the root element we draw to

  const [radius,setRadius] = useState(0);  //initial radius value

  useEffect(() => {

    d3.select(root.current)
      .transition()
      .duration(1000)
      .attr("r", props.width / 2)
      .on("end", () => {setRadius(props.width / 2)});   //this is necessary

  }, [props.width]);

  return (
      <svg width={props.width} height="100%">
        <circle ref={root} cx="0" cy="0" r={radius} fill="red" />
      </svg>
    );
}

我自己也遇到过类似的问题,据我所知,在转换期间,d3 直接对您想要的值进行操作,导致它不能触发 react 的重新渲染机制。但一旦完成,我假设它直接尝试操作半径值,并且您的圆圈会弹回到其 0 原始值。因此,您只需添加一个 .on('end') 事件来更新其状态,就可以解决问题了!
我感觉这已经是最接近 react 的方式了,其中 d3 仅选择基于 react 状态呈现的元素。

谢谢 - 我认为我最关心的是React在幕后进行的DOM差异处理。在第一个示例中,例如,我可以看到React希望根据快照重置圆的半径... - Ian
React在您的第一个示例中是否真正重置了圆形半径?d3转换更改React之外的DOM的问题是不可避免的。然而,React从未做出反应,直到涉及属性或状态更改。通过将属性或状态变量与您的圆形相关联,然后添加on('end')事件以将所有内容设置为应该处于的最终状态,可以减轻d3的更改。 - Zee

1
React渲染的DOM元素可以被修改。但是,如果React需要重新渲染,因为虚拟DOM与当前DOM不匹配(React作为当前DOM),它可能会替换修改的部分,并且在React之外进行的更改可能会丢失。
React修改必要的部分,因此如果React只修改了元素的一部分,则修改可能仍然存在,否则可能会被删除。因此,据我所知,我们不能相信修改将保留。只有当我们确定组件输出在自定义修改后不会发生变化时,才能这样做。
我的建议是使用React跟踪任何更改:
  • 使用useState和useEffect来修改style属性。
  • 使用CSS类并使用CSS处理动画。
  • 使用专为React制作的动画库(请参阅Framer motion)。

嗯,谢谢Alvaro。在这种情况下,我正在使用D3库,对于DOM的那些部分,我无法将控制权交给React,因为我无法停止渲染。如果circle是在useEffect中添加到DOM中的,那么它就不会被包含在JSX部分中 - 这会改变现状吗? - Ian
我已经添加了一些内容到我的问题中,以此来帮助说明。 - Ian
@Ian 我进行了一些测试,使用了与你类似的场景,似乎在React中修改子元素后仍会保留额外的子元素。我猜测React试图执行最小/最有效的操作来更新组件输出:修改属性;添加子元素而不是修改整个innerHTML...所以这可能是为什么它看起来没问题的原因。但正如我在答案中所说,我认为我们不能依赖它。让我们看看是否有人对内部机制有很好的理解并能给出详细的答案。 - Alvaro
感谢@Alvaro的努力,我会点赞的。我可能会联系Dan Abramov以获取他的想法,因为我发现文档不太清楚。 - Ian
@Ian,不用客气,我确实不是专家。如果能从他或团队中的某个人那里得到一个答案,那就太好了。 - Alvaro

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接