你能控制基于状态变化的功能性反应组件的重新渲染吗?
Posted
技术标签:
【中文标题】你能控制基于状态变化的功能性反应组件的重新渲染吗?【英文标题】:Can you control the re-renders of a functional react component based on state change? 【发布时间】:2021-08-25 04:29:20 【问题描述】:我有一个用于练习的基本电子商务应用程序。有一个名为“Shop”的功能组件有两种状态:
[products, setProducts] = useState([10ProductObjects])
and [cart, setCart] = useState([])
在第一个渲染周期中,加载了 10 个产品,每个 Product
组件都有一个“添加到购物车”按钮,用于将产品添加到购物车数组。
当我单击按钮时,会填充购物车数组并显示其长度。但是,这样做会再次重新渲染这 10 个产品,即使它们没有任何更改。
现在我看到了,因为其中一个状态发生了变化,即购物车数组,整个 Shop
组件再次呈现。依次渲染其子组件,包括未更改的子组件。
我尝试使用React.memo
,但它没有帮助,因为“商店”上没有更改任何道具,而是状态正在改变。我还使用了useMemo
钩子,它有一些有趣的结果。
使用 products 数组作为依赖解决了额外的重新渲染问题,但新产品不再添加到购物车中。使用 [products, cart]
作为依赖项可以工作,但会带来原来的问题。
我知道可以使用 shouldComponentUpdate
来完成,但我需要功能组件中的那种灵活性。
注意:这是我的第一个问题,我很乐意听取任何反馈。
import React, useState, useMemo from 'react';
import fakeData from '../../fakeData';
import Product from '../Product/Product';
const Shop = () =>
console.log('[Shop.js] rendered')
const first10 = fakeData.slice(0, 10);
const [products, setProducts] = useState(first10);
const [cart, setCart] = useState([]);
const addProductHandler = (product) =>
console.log(cart, product);
const newCart = [...cart, product];
console.log(newCart);
setCart(newCart);
let productsOnScreen = useMemo(() =>
return products.map( prod =>
return <Product product=prod addProductHandler=addProductHandler />
);
, [products])
return (
<div className="shop-container">
<div className="product-container">
productsOnScreen
</div>
<div className="cart-container">
<h3>this is cart</h3>
<h5>Order Summary: cart.length</h5>
</div>
</div>
);
;
export default Shop;
【问题讨论】:
不要过早优化。您是否有特定的原因要避免重新渲染?如果您正确管理状态和效果,“额外”渲染在 99% 的情况下都不会成为问题。 React 进行了很好的优化,除非在极少数情况下(这不是其中之一),否则您极不可能遇到性能问题。 在这种特殊情况下,既不会更新产品列表,也不会以任何方式更改单个产品。那么为什么要重新渲染它们呢?是的,我有点过早地优化,或者更确切地说是学习优化,因为我更容易在像这样的简单应用程序中掌握理论。感谢您的评论。 即使他们的状态没有改变,他们是否重新渲染也没关系。这是完全正常和正常的,并且一直在发生。只要 render 函数正确地将 state 和 props 转换为正确的渲染输出,一点都没有关系。更担心你的状态、道具和效果,然后是“重新渲染”的次数,这在 99% 的情况下都无关紧要。如果您有任何依赖于已发生的重新渲染次数的逻辑,那么您做错了。 完全没问题,并且期望子组件在其包含的父组件重新渲染时会重新渲染。这就是 React 的工作原理。 我理解您要表达的意思。但我会等着看我是否对这个问题有一些不同的看法,然后再编辑帖子。 【参考方案1】:Memoize addProductHandler
和 React.useCallback
以便对它的引用在渲染之间不会改变:
const addProductHandler = React.useCallback((product) =>
setCart(oldCart =>
return [...oldCart, product];
);
, [setCart]);
然后,用React.memo
记住<Product>
。您没有发布该组件的代码,但它看起来像这样:
export const Product = React.memo((props) =>
// normal functional component stuff
return <>product component or whatever</>;
);
这些都是组件避免不必要的重新渲染所必需的。为什么?
React.useCallback
允许通过引用进行比较以用于渲染之间的回调函数。如果你声明回调函数 every 渲染,这不起作用,就像你目前所做的那样。
React.memo
包装一个组件,使其能够根据其道具的浅显比较来渲染或不渲染。默认情况下,React 组件不这样做,您必须使用 React.memo
显式启用它。
正如文档所述:
此方法仅作为性能优化存在。不要依赖它来“阻止”渲染,因为这可能会导致错误。
换句话说,您应该只在遇到速度变慢的情况下将其用作性能优化,而不是出于任何其他原因阻止渲染,并且仅当您实际上遇到性能问题时。 p>
如果这不起作用,则很可能您的 <Product>
道具之一在渲染之间仍然在变化(在浅层通过引用比较的情况下)。继续玩和记忆道具,直到你弄清楚哪些道具在渲染之间发生了变化。一种测试方法是在<Product>
中添加一个简单的React.useEffect
(仅用于调试目的),它会在道具发生变化时提醒您:
React.useEffect(() =>
console.log('product prop changed');
, [product]);
React.useEffect(() =>
console.log('addProductHandler prop changed');
, [addProductHandler]);
// etc...
【讨论】:
非常感谢,这就像一个魅力。我将深入研究 React.useCallback 以更好地理解它。但我会牢记文档中引用的部分。以上是关于你能控制基于状态变化的功能性反应组件的重新渲染吗?的主要内容,如果未能解决你的问题,请参考以下文章