跳过效果不适用于动态 URL 的数组

Skipping Effects does not work for Array of dynamic URL's

我有一个 React.SFC/react 无状态/功能组件,不幸的是,由于 parent 组件中来自 redux 的一些多余数据,它呈现得有点太频繁了。目前我对此无能为力,所以我只是接受额外的重新渲染,并使用 useEffect 确保仅在某个 属性 更改时才获取数据。在这种情况下,它称为 "urls",它是 URL 的数组(TypeScript URL 类型)。

下面是一些说明该问题的示例代码:

import React from "react";
import { useState, useEffect, useMemo } from "react";
import { render } from "react-dom";

import "./styles.css";

const useCustomHook = urls => {
  const [onlyChangeWhenUrlsChange, setOnlyChangeWhenUrlsChange] = useState(
    null
  );
  useEffect(
    () => {
      setOnlyChangeWhenUrlsChange(Math.random());
    },
    [urls]
  );
  return onlyChangeWhenUrlsChange;
};

const dynamicUrls = (pageRouteParamId, someDynamicUrlParam) => {
  return [
    {
      pageRouteParamId: 1337,
      urls: [new URL(`https://someurl.com/api?id=${someDynamicUrlParam}`)]
    }
  ];
};

const SomePage: React.SFC<any> = ({
  simulateFrequentUpdatingData,
  pageRouteParamId
}) => {
  const someOtherId = 1;
  // As suggested in SO answer, using useMemo seems to work, but will that not create a memory leak?
  // Is there any good alternative?
  // const urls = useMemo(() => dynamicUrls(pageRouteParamId, someOtherId).find(url => url.pageRouteParamId === pageRouteParamId).urls, [pageRouteParamId, someOtherId]);
  const urls = dynamicUrls(pageRouteParamId, 1).find(
    url => url.pageRouteParamId === 1337
  ).urls;
  return (
    <div>
      <p>parent</p>
      <p>{simulateFrequentUpdatingData}</p>
      <p>
        Page route param id (in real app this would come from react-router route
        param): {pageRouteParamId}
      </p>
      {urls && urls.length && <MyStateLessFunctionalComponent {...{ urls }} />}
      <p>
        Page route param id (in real app this would come from react-router route
        param): {pageRouteParamId}
      </p>
      {urls && urls.length && (
        <MyStateLessFunctionalComponentWithHook {...{ urls }} />
      )}
    </div>
  );
};

const MyStateLessFunctionalComponent: React.SFC<any> = ({ urls }) => {
  const [onlyChangeWhenUrlsChange, setOnlyChangeWhenUrlsChange] = useState(
    null
  );
  useEffect(
    () => {
      setOnlyChangeWhenUrlsChange(Math.random());
    },
    [urls]
  );
  return (
    <div>
      <p>MyStateLessFunctionalComponent</p>
      <p>{JSON.stringify(urls)}</p>
      <p>This should only change when urls change {onlyChangeWhenUrlsChange}</p>
    </div>
  );
};

const MyStateLessFunctionalComponentWithHook: React.SFC<any> = ({ urls }) => {
  const onlyChangeWhenUrlsChange = useCustomHook(urls);
  return (
    <div>
      <p>MyStateLessFunctionalComponentWithHook</p>
      <p>{JSON.stringify(urls)}</p>
      <p>This should only change when urls change {onlyChangeWhenUrlsChange}</p>
    </div>
  );
};

function App() {
  const [
    simulateFrequentUpdatingData,
    setSimulateFrequentUpdatingData
  ] = useState(null);
  const [pageRouteParamId, setPageRouteParamId] = useState(1337);
  useEffect(() => {
    setInterval(() => setSimulateFrequentUpdatingData(Math.random()), 1000);
  }, []);

  return (
    <div className="App">
      <SomePage {...{ simulateFrequentUpdatingData, pageRouteParamId }} />
    </div>
  );
}

const rootElement = document.getElementById("root");
render(<App />, rootElement);

编辑:

我不得不更改标题和问题,因为在使用示例代码重现它时我意识到问题不在于 "Skipping Effects inside a custom hook"。在我看到直接使用 useEffect 与在自定义挂钩内部使用时有所不同之前,正如评论中正确提到的那样,应该没有任何区别 - 我在使用此示例代码重现我的问题时得出了相同的结论:

您可以查看 live example here

正如下面答案中所建议的那样,useMemo 似乎解决了这个问题(见第 36 行)

我的猜测是 urls 是在树中更高层的渲染中声明的,因此每次都会获得一个新的标识。您可以在声明它的地方 useMemo,在 deps 数组中 JSON.stringify urls,或者 useRef 作为额外的防范措施运行。

编辑:比我聪明的人正在讨论这个问题:https://github.com/facebook/react/issues/14476#issuecomment-471199055

如果 urls 是一个字符串数组,您可以将其作为第二个参数传递给 useEffect

useEffect(() => {
  loadData();
}, urls);

这样它将检查字符串值而不是数组引用。