我有一个React.SFC /react stateless /function组件,不幸的是,由于父组件中的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);
编辑:
我不得不更改标题和问题,因为在使用示例代码重现它时,我意识到问题与"在自定义钩子中跳过效果"无关。在我之前,虽然我在直接使用 useEffect 与在自定义钩子中使用时看到了差异,并且正如注释正确提到的,应该没有任何区别 - 我在使用此示例代码重现我的问题时得出了相同的结论:
您可以在此处查看实时示例。
正如下面的答案中所建议的那样,useMemo似乎解决了这个问题(见第36行)
我的猜测是,urls
是在树上更高的渲染中声明的,因此每次都会获得一个新标识。您可以在声明它的位置useMemo
,JSON.stringify
deps-array 中的urls
,或者useRef
作为防止重新运行的额外保护措施。
编辑:这正在被比我更聪明的人讨论:https://github.com/facebook/react/issues/14476#issuecomment-471199055。
如果urls
是一个字符串数组,你可以将其作为第二个参数传递到useEffect。
useEffect(() => {
loadData();
}, urls);
这样,它将检查字符串值而不是数组引用。