ReactJS useEffect与Deps相反

时间:2020-08-29 14:00:49

标签: javascript reactjs react-hooks

通常,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中,因此,如果发生渲染, 这意味着某些图表道具已更改,因此需要重新创建或更新图表。

2 个答案:

答案 0 :(得分:2)

没有一种简单的方法来还原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();
  }
});

答案 1 :(得分:-1)

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

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>
  );
}