你能根据状态变化控制功能性 React 组件的重新渲染吗?

Can you control the re-renders of a functional react component based on state change?

我有一个用于练习的基本电子商务应用程序。有一个名为“商店”的功能组件,它有两种状态:

[products, setProducts] = useState([10ProductObjects])
and [cart, setCart] = useState([])

在第一个渲染周期中,加载了 10 个产品,每个 Product 组件都有一个“添加到购物车”按钮,用于将产品添加到购物车数组。 当我单击按钮时,购物车数组被填充并显示其长度。但是,这样做会再次重新呈现 10 个产品,即使它们没有任何更改。

现在,正如我所见,由于其中一个状态发生了变化,即购物车数组,整个 Shop 组件再次呈现。依次呈现其子组件,包括未更改的子组件。

我试过使用 React.memo 但它没有帮助,因为“商店”上没有道具被改变,而是状态正在改变。我还使用了 useMemo 钩子,它产生了一些有趣的结果。

使用产品数组作为依赖解决了额外的重新渲染问题,但新产品不再添加到购物车。同时使用 [products, cart] 作为依赖项可以工作,但又会出现原来的问题。

我知道可以使用 shouldComponentUpdate 来完成,但我需要功能组件的那种灵活性。

N.B:这是我的第一个问题,我会倾听任何反馈。

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;

React.useCallback 记忆 addProductHandler 以便对它的引用不会在渲染之间改变:

const addProductHandler = React.useCallback((product) => {
    setCart(oldCart => {
        return [...oldCart, product];
    });
}, [setCart]);

然后,用 React.memo 记住 <Product>。您没有 post 该组件的代码,但它看起来像这样:

export const Product = React.memo((props) => {
  // normal functional component stuff

  return <>product component or whatever</>;
});

这些都是组件避免不必要的重新渲染所必需的。为什么?

  1. React.useCallback 允许通过引用进行比较以用于渲染之间的回调函数。如果您声明回调函数 every 渲染,这将不起作用,就像您目前所做的那样。
  2. React.memo 包装组件以使其能够呈现或 呈现取决于其道具的浅层比较。 React 组件默认情况下不会这样做,您必须使用 React.memo.
  3. 显式启用它

如文档所述:

This method only exists as a performance optimization. Do not rely on it to “prevent” a render, as this can lead to bugs.

换句话说,您应该只在遇到速度下降时将其用作性能优化,而不是为了出于任何其他原因阻止渲染,并且仅当您实际上遇到性能问题。

如果这不起作用,很可能您的 <Product> 道具之一 仍在 变化(在浅层引用比较的上下文中)渲染。继续玩弄和记忆道具,直到你弄清楚哪些道具在渲染之间发生了变化。一种测试方法是在 <Product> 中添加一个简单的 React.useEffect(仅用于调试目的),它会在 prop 发生变化时提醒您:

React.useEffect(() => {
    console.log('product prop changed');
}, [product]);

React.useEffect(() => {
    console.log('addProductHandler prop changed');
}, [addProductHandler]);

// etc...