React.memo失效:父组件更新时子组件仍重复渲染问题
子组件已通过React.memo()导出,预期父组件更新时子组件不会重新渲染,但实际父组件更新时,子组件仍会渲染(控制台打印"Children Render")。如何阻止父组件更新时子组件的不必要渲染?
父组件代码
import React, { useState } from "react"; import Children from "./Children"; function Parent() { const [name, setName] = useState(""); const [products, setProducts] = useState([{ name: "", price: "" }]); console.log("parent render"); function handleNameChange(event) { setName(event.target.value); } function handleProductChange(index, product) { setProducts((prev) => { const newProducts = [...prev]; newProducts[index] = product; return newProducts; }); } function addAnotherProduct() { setProducts((prev) => { const newProducts = prev; return [...newProducts, { name: "", price: "" }]; }); } return ( <> <label>Category:</label> <input type="text" value={name} name="name" onChange={handleNameChange} /> <br /> {products.map((product, index) => { return ( <Children key={index} name={product.name} price={product.price} handleProductChange={(product) => handleProductChange(index, product) } /> ); })} <button onClick={addAnotherProduct}>Add another product</button> </> ); } export default Parent;
子组件代码
import React from "react"; function Children(props) { const productName = props.name; const productPrice = props.price; const handleChange = (name, value) => { const newProduct = { name: productName, price: productPrice, [name]: value }; console.log(newProduct); props.handleProductChange(newProduct); }; console.log("Children Render"); return ( <> <div> <label>Product Name:</label> <input type="text" value={productName} onChange={(event) => handleChange("name", event.target.value)} /> <br /> <label>Product Price:</label> <input type="text" value={productPrice} onChange={(event) => handleChange("price", event.target.value)} /> </div> </> ); } export default React.memo(Children);
已尝试使用React.memo(Children),并为父组件的handleProductChange()添加useCallback(),但均未解决问题。
问题核心在于:父组件每次渲染时,都会给子组件传递新的匿名函数(product) => handleProductChange(index, product)——哪怕外层的handleProductChange用了useCallback,这个包裹的匿名函数还是会在每次父组件渲染时重新创建,导致React.memo的浅比较认为props发生变化,触发子组件重渲染。
具体修复步骤:
用
useCallback缓存父组件的handleProductChange函数
确保该函数在父组件渲染时不会被重新创建,依赖项仅保留稳定的setProducts(React中useState返回的 setter 函数天生稳定,不会随组件渲染变化)。直接传递缓存后的函数给子组件,同时将
index作为独立props传入
避免在map中创建新的匿名函数,让子组件自己携带index参数调用父函数。修改子组件逻辑,调用时传入
index和更新后的product
修改后的代码:
父组件
import React, { useState, useCallback } from "react"; import Children from "./Children"; function Parent() { const [name, setName] = useState(""); const [products, setProducts] = useState([{ name: "", price: "" }]); console.log("parent render"); function handleNameChange(event) { setName(event.target.value); } // 用useCallback缓存函数,依赖项为空数组(setProducts是稳定的) const handleProductChange = useCallback((index, product) => { setProducts((prev) => { const newProducts = [...prev]; newProducts[index] = product; return newProducts; }); }, []); function addAnotherProduct() { setProducts((prev) => { return [...prev, { name: "", price: "" }]; }); } return ( <> <label>Category:</label> <input type="text" value={name} name="name" onChange={handleNameChange} /> <br /> {products.map((product, index) => { return ( <Children key={index} // 建议后续替换为唯一id,比如给每个product加id字段 index={index} name={product.name} price={product.price} handleProductChange={handleProductChange} /> ); })} <button onClick={addAnotherProduct}>Add another product</button> </> ); } export default Parent;
子组件
import React from "react"; function Children(props) { const { name: productName, price: productPrice, index, handleProductChange } = props; const handleChange = (field, value) => { const newProduct = { name: productName, price: productPrice, [field]: value }; // 调用时传入index和newProduct handleProductChange(index, newProduct); }; console.log("Children Render"); return ( <> <div> <label>Product Name:</label> <input type="text" value={productName} onChange={(event) => handleChange("name", event.target.value)} /> <br /> <label>Product Price:</label> <input type="text" value={productPrice} onChange={(event) => handleChange("price", event.target.value)} /> </div> </> ); } export default React.memo(Children);
验证效果
现在修改父组件的Category输入框时,子组件不会再打印"Children Render",只有当修改对应子组件的产品名称/价格,或者添加新产品时,对应子组件才会重新渲染。
内容的提问来源于stack exchange,提问作者tata

