ReactJS useEffect与Deps相反

问题描述

通常,effect中的React.useEffect的{​​{1}}仅在deps列表中的值更改时被激活。

我想要的是相反的。我希望它被激活,除非列表更改中的值

顺便说一句,我知道chart.js周围的包装器已经存在。

但是,人们要求一个具体的例子,所以在这里

例如:

import React,{ useState,useEffect,memo,useMemo } from "react";
import merge from "lodash/merge";
import {
  Chart as ChartJS,ChartType,ChartData,ChartOptions,ChartConfiguration,} from "chart.js";

type ChartInstance = InstanceType<typeof ChartJS> | null;

export type ChartProps = Readonly<ChartData> &
  Readonly<ChartOptions> & {
    readonly type?: ChartType;
    readonly height?: number | string;
  };

export const Chart = memo<ChartProps>(
  ({
    type = "line",height = "20em",responsive = true,maintainAspectRatio = false,animation = { duration: 0 },hover = { animationDuration: 0 },responsiveAnimationDuration = 0,labels = [],datasets = [],legend: { display = false,...legend } = {},...rest
  }) => {
    const [canvas,ref] = useState<HTMLCanvasElement | null>(null);
    let chart: ChartInstance = null;

    const options: ChartConfiguration = {
      type,data: { labels,datasets },options: {
        responsive,maintainAspectRatio,animation,hover,responsiveAnimationDuration,legend: { display,...legend },...rest,},};

    const destroy = () => {
      if (chart) {
        chart.destroy();
        chart = null;
      }
    };

    chart = useMemo<ChartInstance>(() => {
      destroy();
      return canvas ? new ChartJS(canvas,options) : null;
    },[canvas,type]);

    useEffect(() => {
      if (chart) {
        merge(chart,options).update();
      }
    });

    useEffect(() => destroy,[]);

    return (
      <div style={{ position: "relative",height }}>
        <canvas ref={ref} />
      </div>
    );
  }
);

如果我们只是创建图表,则更新图表是没有意义的。

如果执行useMemo,则后面的useEffect不应该执行。 否则,它需要每次执行。

该组件包装在React.memo中,因此,如果发生渲染, 这意味着某些图表道具已更改,因此需要重新创建或更新图表。

解决方法

没有一种简单的方法来还原useEffect依赖性检查行为。可以做到,但这将是不必要的hack,尤其是您不使用清理功能。

在您的情况下,最直接的方法是引入一个变量(shouldUpdateChart),该变量将控制是否应运行useEffect代码。如果运行useMemo中的代码,则其值为false,否则为true

let shouldUpdateChart = true;

chart = useMemo < ChartInstance > (() => {
  shouldUpdateChart = false;
  destroy();
  return canvas ? new ChartJS(canvas,options) : null;
},[canvas,type]);

useEffect(() => {
  if (shouldUpdateChart) {
    merge(chart,options).update();
  }
});
,

一种选择是维持对变量先前值的一些外部引用,在每个渲染器上运行效果,然后在变量发生更改时纾困。这是一个可能如何工作的示例。您可以将此逻辑抽象为自己的自定义钩子。

let prevA;

export default function App() {
  const [a,setA] = useState(0);
  const [b,setB] = useState(0);

  useEffect(() => {
    if (a !== prevA) {
      prevA = a;
      return;
    }
    console.log("a did not change");
  });

  return (
    <div className="App">
      <button
        onClick={() => {
          setA(a + 1);
        }}
      >
        Increment A
      </button>
      <button
        onClick={() => {
          setB(b + 1);
        }}
      >
        Increment B
      </button>
    </div>
  );
}