前言

由于之前Vue开发的习惯,所有函数都定义成了const函数,而在useEffect中,如果不再依赖中引入这些函数eslint就会报warning,但遇到了一些问题,useEffect在不期望的时候执行多遍

问题

有如下代码:

依赖中既有redux中数据,又有常量函数updateGraph,测试发现额外的点击,会触发这段代码的多次执行,比如我切换侧边栏,引发了侧边栏的重新渲染,这段代码就会不必要的执行一次,令人费解

useEffect(() => {
    messageApi.info('waitEdgeNodes.length:' + waitEdgeNodes.length);
    //...
    }, [waitEdgeNodes.length, updateGraph]);

原因

在组件的主入口有如下代码,切换侧边栏时,由于顶层组件拥有状态option,所以它的改变会导致子组件的重新渲染,而发生问题的组件就是子组件<SysmlGraph />

      <GraphContext.Provider
        value={{
          option,
          setOption,
        }}
      >
        <Layout>
          <Layout>
            <Header>
              <GraphToolbar
              ></GraphToolbar>
            </Header>
            <div>
              <SysmlGraph setGraph={setGraphInstance} />
            </div>
          </Layout>
          <Sider>
            <GraphSiderHeader></GraphSiderHeader>
            {option === 'lib' && <SysmlStencil />}
            {option === 'page' && <GraphSiderPageStyle></GraphSiderPageStyle>}
            {option === 'style' && <GraphSiderGraphStyle></GraphSiderGraphStyle>}
          </Sider>
        </Layout>
      </GraphContext.Provider>

在React中,每当组件重新渲染时,其内部的所有代码,包括函数的定义和状态的创建,都会被重新执行,这也包括常量函数的定义,这也就解释了为什么常量函数updateGraph作为上述useEffect依赖时,会因为非父子组件侧边栏的切换而导致useEffect不必要的执行

解决

使用useCallback,会在组件挂载时创建一个稳定的函数引用,并且在依赖项不变的情况下保持相同的引用,这样做可以避免不必要的函数重复创建,提高性能。

即如果updateGraph的实现和逻辑在组件的生命周期中保持不变,并且希望在组件的重新渲染时保持相同的函数引用,useCallback可以提高性能

//更新画布发送请求后端保存
const updateGraph = useCallback(
    (cell: Cell) => {
        //生成虚拟边不保存
        if (cell && cell.id === 'dummy-edge') return;
        updateSysML({ id: graphId, cells: graph.toJSON().cells }).then(res => {
            if (res.code !== 200) {
                messageApi.error(res.msg);
            }
        });
    },
    [graphId, messageApi],
);

小结

比较

常量函数适用于简单的函数逻辑,不需要在组件重新渲染时保持相同引用的情况。(例如:响应点击事件的函数,不需要在useEffect里调用)

useCallback适用于需要优化性能的情况,特别是当将函数作为回调传递给子组件时,可以使用useCallback来确保子组件在依赖项不变的情况下不会重新渲染。

内存泄漏

如果不将常量函数updateGraph加入到useEffect依赖项中时,按照正常的思维逻辑,useEffect不依赖与常量函数的变化,并不会导致不必要的执行。

从效果上来看这样写是不会出现逻辑上的问题,但由于缺乏依赖项,仍会导致其他隐性的问题:

useEffect中使用常量函数时,常量函数会捕获组件作用域中的变量,形成闭包,即内部引用了组件作用域中的某些变量。

当组件重新渲染时,常量函数会重新被创建,由于其没有被添加到依赖数组中,useEffect会在每次组件重新渲染时继续引用前一个渲染周期的常量函数。由于该常量函数包含对就组件作用域中的变量引用(即形成了闭包),这将导致旧的变量一直保持活动状态,无法被垃圾回收,即时组件被销毁,它们也会继续占用内存,导致内存泄漏


0 条评论

发表回复

Avatar placeholder

您的电子邮箱地址不会被公开。 必填项已用*标注