跳过效果不适用于动态网址数组

时间:2019-03-16 12:29:17

标签: reactjs react-hooks

我有一个React.SFC /无状态状态/功能组件,不幸的是由于父组件中的redux引入了一些多余的数据而使得渲染过于频繁。目前,我对此无能为力,所以我只是接受额外的重现,并使用useEffect来确保仅在特定属性发生更改时才获取数据。在这种情况下,它称为“ URL”,它是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);

编辑:

我不得不更改标题和问题,因为在用示例代码进行复制时,我意识到问题不在于“在自定义钩子中跳过效果”。在我看到直接使用useEffect与在自定义钩子内部使用时有所不同之前,正如正确提到的注释一样,应该没有任何区别-在用以下示例代码重现我的问题时,我得出了相同的结论:

您可以签出live example here

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

2 个答案:

答案 0 :(得分:1)

我的猜测是urls是在树的更高渲染器中声明的,因此每次都会获得一个新的标识。您可以在要声明的地方useMemo,在deps数组中JSON.stringifyurls,或者useRef,它可以防止重新运行。

编辑:https://github.com/facebook/react/issues/14476#issuecomment-471199055比我更聪明。

答案 1 :(得分:0)

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

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

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