Rya*_*Sam 67 javascript fetch reactjs react-hooks
获取数据时,我得到:无法对未安装的组件执行 React 状态更新。该应用程序仍然有效,但反应表明我可能导致内存泄漏。
这是一个空操作,但它表明您的应用程序中存在内存泄漏。要修复,请取消 useEffect 清理函数中的所有订阅和异步任务。”
为什么我不断收到此警告?
我尝试研究这些解决方案:
https://developer.mozilla.org/en-US/docs/Web/API/AbortSignal
https://developer.mozilla.org/en-US/docs/Web/API/AbortController
但这仍然给了我警告。
const ArtistProfile = props => {
const [artistData, setArtistData] = useState(null)
const token = props.spotifyAPI.user_token
const fetchData = () => {
const id = window.location.pathname.split("/").pop()
console.log(id)
props.spotifyAPI.getArtistProfile(id, ["album"], "US", 10)
.then(data => {setArtistData(data)})
}
useEffect(() => {
fetchData()
return () => { props.spotifyAPI.cancelRequest() }
}, [])
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
}
Run Code Online (Sandbox Code Playgroud)
编辑:
在我的 api 文件中,我添加了一个AbortController()并使用了一个,signal以便我可以取消请求。
export function spotifyAPI() {
const controller = new AbortController()
const signal = controller.signal
// code ...
this.getArtist = (id) => {
return (
fetch(
`https://api.spotify.com/v1/artists/${id}`, {
headers: {"Authorization": "Bearer " + this.user_token}
}, {signal})
.then(response => {
return checkServerStat(response.status, response.json())
})
)
}
// code ...
// this is my cancel method
this.cancelRequest = () => controller.abort()
}
Run Code Online (Sandbox Code Playgroud)
我的spotify.getArtistProfile()看起来像这样
this.getArtistProfile = (id,includeGroups,market,limit,offset) => {
return Promise.all([
this.getArtist(id),
this.getArtistAlbums(id,includeGroups,market,limit,offset),
this.getArtistTopTracks(id,market)
])
.then(response => {
return ({
artist: response[0],
artistAlbums: response[1],
artistTopTracks: response[2]
})
})
}
Run Code Online (Sandbox Code Playgroud)
但是因为我的信号用于单独的 api 调用,Promise.all我不能abort()承诺,所以我将始终设置状态。
小智 37
对我来说,清理组件卸载中的状态有帮助。
const [state, setState] = useState({});
useEffect(() => {
myFunction();
return () => {
setState({}); // This worked for me
};
}, []);
const myFunction = () => {
setState({
name: 'Jhon',
surname: 'Doe',
})
}
Run Code Online (Sandbox Code Playgroud)
ᆼᆺᆼ*_*ᆼᆺᆼ 30
AbortController在fetch()请求之间共享是正确的方法。
当所有的的Promises的中止,Promise.all()将拒绝AbortError:
function Component(props) {
const [fetched, setFetched] = React.useState(false);
React.useEffect(() => {
const ac = new AbortController();
Promise.all([
fetch('http://placekitten.com/1000/1000', {signal: ac.signal}),
fetch('http://placekitten.com/2000/2000', {signal: ac.signal})
]).then(() => setFetched(true))
.catch(ex => console.error(ex));
return () => ac.abort(); // Abort both fetches on unmount
}, []);
return fetched;
}
const main = document.querySelector('main');
ReactDOM.render(React.createElement(Component), main);
setTimeout(() => ReactDOM.unmountComponentAtNode(main), 1); // Unmount after 1msRun Code Online (Sandbox Code Playgroud)
<script src="//cdnjs.cloudflare.com/ajax/libs/react/16.8.3/umd/react.development.js"></script>
<script src="//cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.3/umd/react-dom.development.js"></script>
<main></main>Run Code Online (Sandbox Code Playgroud)
mic*_*nil 14
为什么我不断收到此警告?
此警告的目的是帮助您防止应用程序中的内存泄漏。如果组件在从 DOM 卸载后更新其状态,则表明可能存在内存泄漏,但这表明存在大量误报。
我如何知道我是否存在内存泄漏?
如果一个对象的生命周期比组件的寿命长,则直接或间接地持有对其的引用,则存在内存泄漏。当您订阅事件或某种类型的更改而不在组件从 DOM 卸载时取消订阅时,通常会发生这种情况。
它通常看起来像这样:
useEffect(() => {
function handleChange() {
setState(store.getState())
}
// "store" lives longer than the component,
// and will hold a reference to the handleChange function.
// Preventing the component to be garbage collected after
// unmount.
store.subscribe(handleChange)
// Uncomment the line below to avoid memory leak in your component
// return () => store.unsubscribe(handleChange)
}, [])
Run Code Online (Sandbox Code Playgroud)
一个对象位于storeReact 树的更上层(可能在上下文提供者中),或者在全局/模块范围中。另一个例子是订阅事件:
useEffect(() => {
function handleScroll() {
setState(window.scrollY)
}
// document is an object in global scope, and will hold a reference
// to the handleScroll function, preventing garbage collection
document.addEventListener('scroll', handleScroll)
// Uncomment the line below to avoid memory leak in your component
// return () => document.removeEventListener(handleScroll)
}, [])
Run Code Online (Sandbox Code Playgroud)
另一个值得记住的例子是Web APIsetInterval,如果在卸载时忘记调用,也可能导致内存泄漏clearInterval。
但这不是我正在做的事情,我为什么要关心这个警告呢?
React 的策略是在组件卸载后每当状态更新发生时发出警告,这会产生大量误报。我见过的最常见的是在异步网络请求后设置状态:
async function handleSubmit() {
setPending(true)
await post('/someapi') // component might unmount while we're waiting
setPending(false)
}
Run Code Online (Sandbox Code Playgroud)
从技术上讲,您可能会认为这也是内存泄漏,因为组件在不再需要后不会立即释放。如果您的“帖子”需要很长时间才能完成,那么释放内存也需要很长时间。然而,这不是你应该担心的事情,因为它最终会被垃圾收集。在这些情况下,您可以简单地忽略该警告。
但是看到这个警告实在是太烦人了,如何去掉呢?
stackoverflow 上有很多博客和答案建议跟踪组件的安装状态并将状态更新包装在 if 语句中:
let isMountedRef = useRef(false)
useEffect(() => {
isMountedRef.current = true
return () => {
isMountedRef.current = false
}
}, [])
async function handleSubmit() {
setPending(true)
await post('/someapi')
if (!isMountedRef.current) {
setPending(false)
}
}
Run Code Online (Sandbox Code Playgroud)
这不是推荐的方法!它不仅会降低代码的可读性并增加运行时开销,而且可能无法与 React 的未来功能很好地配合。它对“内存泄漏”也没有任何作用,组件仍然会像没有额外代码一样长时间存在。
处理这个问题的推荐方法是取消异步函数(例如使用AbortController API),或者忽略它。
事实上,React 开发团队认识到避免误报太困难,并在 React v18 中删除了警告。
Dmi*_*hin 10
例如,您有一些组件执行一些异步操作,然后将结果写入 state 并在页面上显示 state 内容:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
// ...
useEffect(() => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it needs some time
// When request is finished:
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<a href="SOME_LOCAL_LINK">Go away from here!</a>
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
假设用户在doVeryLongRequest()仍然执行时单击了某个链接。MyComponent已卸载但请求仍然存在,当它收到响应时,它会尝试在第(1)和(2)行中设置状态并尝试更改 HTML 中的相应节点。我们会从主题中得到一个错误。
我们可以通过检查组件是否仍然安装来修复它。让我们创建一个变量componentMounted(下面的第(3)行)并设置它true。卸载组件后,我们会将其设置为false(下面的第(4)行)。让componentMounted我们每次尝试设置状态时检查变量(下面的第(5)行)。
修复代码:
export default function MyComponent() {
const [loading, setLoading] = useState(false);
const [someData, setSomeData] = useState({});
let componentMounted = true; // (3) component is mounted
// ...
useEffect(() => {
setLoading(true);
someResponse = await doVeryLongRequest(); // it needs some time
// When request is finished:
if (componentMounted){ // (5) is component still mounted?
setSomeData(someResponse.data); // (1) write data to state
setLoading(false); // (2) write some value to state
}
return () => { // This code runs when component is unmounted
componentMounted = false; // (4) set it to false if we leave the page
}
}, []);
return (
<div className={loading ? "loading" : ""}>
{someData}
<a href="SOME_LOCAL_LINK">Go away from here!</a>
</div>
);
}
Run Code Online (Sandbox Code Playgroud)
您可以尝试设置这样的状态并检查您的组件是否已安装。这样您就可以确定,如果您的组件已卸载,您就不会尝试获取某些东西。
const [didMount, setDidMount] = useState(false);
useEffect(() => {
setDidMount(true);
return () => setDidMount(false);
}, [])
if(!didMount) {
return null;
}
return (
<ArtistProfileContainer>
<AlbumContainer>
{artistData ? artistData.artistAlbums.items.map(album => {
return (
<AlbumTag
image={album.images[0].url}
name={album.name}
artists={album.artists}
key={album.id}
/>
)
})
: null}
</AlbumContainer>
</ArtistProfileContainer>
)
Run Code Online (Sandbox Code Playgroud)
希望这会帮助你。
小智 5
我遇到了类似的问题,滚动到顶部,@CalosVallejo 的回答解决了它:) 非常感谢!
const ScrollToTop = () => {
const [showScroll, setShowScroll] = useState();
//------------------ solution
useEffect(() => {
checkScrollTop();
return () => {
setShowScroll({}); // This worked for me
};
}, []);
//----------------- solution
const checkScrollTop = () => {
setShowScroll(true);
};
const scrollTop = () => {
window.scrollTo({ top: 0, behavior: "smooth" });
};
window.addEventListener("scroll", checkScrollTop);
return (
<React.Fragment>
<div className="back-to-top">
<h1
className="scrollTop"
onClick={scrollTop}
style={{ display: showScroll }}
>
{" "}
Back to top <span>⟶ </span>
</h1>
</div>
</React.Fragment>
);
};Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
160686 次 |
| 最近记录: |