我有一个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行)
答案 0 :(得分:1)
我的猜测是urls
是在树的更高渲染器中声明的,因此每次都会获得一个新的标识。您可以在要声明的地方useMemo
,在deps数组中JSON.stringify
,urls
,或者useRef
,它可以防止重新运行。
编辑:https://github.com/facebook/react/issues/14476#issuecomment-471199055比我更聪明。
答案 1 :(得分:0)
如果urls
是字符串数组,则可以将其作为useEffect的第二个参数传递
useEffect(() => {
loadData();
}, urls);
那样,它将检查字符串值而不是数组引用。