React Hooks组件如何清理订阅?解决异步更新内存泄漏与重复渲染问题
修复方案
问题根源
- 无限重复渲染:
HomeScreen组件的useEffect依赖数组错误包含了products状态。你在useEffect内部调用setProducts会修改products值,而products变更会再次触发useEffect执行,形成死循环,导致重复请求、重复渲染。 - 内存泄漏警告:未处理异步请求的中断逻辑,若组件在请求返回前卸载,请求完成后依然会尝试更新已卸载组件的状态,触发警告。
修复步骤
- 修正
HomeScreen的useEffect依赖数组为空白数组,确保仅在组件首次挂载时发起一次请求。 - 两个组件的请求逻辑新增
AbortController中断处理,在useEffect清理函数中终止未完成的请求,避免组件卸载后执行状态更新。 ProductScreen的useEffect依赖调整为match.params.id,比依赖完整match对象更稳定,避免不必要的重渲染。
修复后代码
HomeScreen.jsx
import React , { useState, useEffect } from 'react' import { Row , Col } from 'react-bootstrap' import Product from '../components/Product' import axios from 'axios' const HomeScreen = () =>{ const [products , setProducts] = useState([]) useEffect(()=>{ const controller = new AbortController() const fetchProducts = async () =>{ try { const {data} = await axios.get('http://localhost:5000/api/products', { signal: controller.signal }) setProducts(data) } catch (err) { // 忽略请求主动中断的报错 if (err.name !== 'CanceledError') { console.error(err) } } } fetchProducts() // 组件卸载时中断未完成的请求 return () => controller.abort() },[]) return( <> <h2 className='my-3'>Latest Products</h2> <Row> { products.map((product)=>( <Col key={product._id} sm={12} md={6} lg={4} xl={3}> <Product product={product} rating = {product.rating} reviews={product.numReviews}/> </Col> )) } </Row> </> ) } export default HomeScreen
ProductScreen.jsx
import React , {useState , useEffect} from 'react' import { Row, Col, Image, ListGroup, Card, Button } from 'react-bootstrap' import {Link} from 'react-router-dom' import Rating from '../components/Rating' import axios from 'axios' const ProductScreen = ({match}) => { const [product , setProduct] = useState({}) useEffect(()=>{ const controller = new AbortController() const fetchProduct = async ()=>{ try { const {data} = await axios.get(`http://localhost:5000/api/products/${match.params.id}`, { signal: controller.signal }) setProduct(data) } catch (err) { if (err.name !== 'CanceledError') { console.error(err) } } } fetchProduct() return () => controller.abort() },[match.params.id]) return ( <> <Link className='btn btn-light my-3' to='/'> Go Back </Link> <Row> <Col md={6}> <Image src={product.image} alt={product.name} /> </Col> <Col md={3}> <ListGroup variant='flush'> <ListGroup.Item> <h3>{product.name}</h3> </ListGroup.Item> <ListGroup.Item> <Rating rating={product.rating} reviews={product.numReviews}/> </ListGroup.Item> <ListGroup.Item> <strong> Price: ${product.price}</strong> </ListGroup.Item> <ListGroup.Item> <strong>Description :</strong> {product.description} </ListGroup.Item> </ListGroup> </Col> <Col md={3}> <Card> <ListGroup> <ListGroup.Item> <Row> <Col> Price : </Col> <Col> <strong>${product.price}</strong> </Col> </Row> </ListGroup.Item> <ListGroup.Item> <Row> <Col> Status : </Col> <Col> <strong>{product.countInStock > 0 ? 'In Stock' : "Out Of Stock"}</strong> </Col> </Row> </ListGroup.Item> <ListGroup.Item className='d-grid gap-2'> <Button type='button' disabled={product.countInStock === 0}> Add To Cart </Button> </ListGroup.Item> </ListGroup> </Card> </Col> </Row> </> ) } export default ProductScreen
内容的提问来源于stack exchange,提问作者Mohammed Alhawamdeh
相关产品推荐
相关产品推荐

