跳过效果不适用于动态 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);
这样它将检查字符串值而不是数组引用。
我有一个 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);
这样它将检查字符串值而不是数组引用。