React.useMemo正在重新呈现数组中的所有项



我有一个react组件,它在useState中存储一组水果。我有一个记忆功能(visibleFruits(可以过滤水果。我将visibleFruits映射到dom。

问题是,当我检查一个水果时,所有可见的水果都会重新渲染。

我希望只有选定的一个重新渲染,因为它是唯一一个正在更改的。

有没有一种方法可以让我使用这个模式,但防止所有的东西在检查时重新呈现?

在现实生活中,visibleFruits useMemo有一个复杂的功能。所以我不能简单地在映射之前附加过滤器。

编辑,这里是更新的编辑:


const Example = () => {
const [fruits, setFruits] = React.useState([
{ id: 1, name: 'apple', visible: true, selected: false },
{ id: 2, name: 'banana', visible: false, selected: false },
{ id: 3, name: 'orange', visible: true, selected: false }
])
const visibleFruits = React.useMemo(() => {
return fruits.filter((f) => f.visible)
}, [fruits])
const handleCheck = (bool, id) => {
setFruits((prev) => {
return prev.map((f) => {
if (f.id === id) {
f.selected = bool
}
return f
})
})
}
return (
<div>
{visibleFruits.map((fruit) => {
return <FruitOption fruit={fruit} handleCheck={handleCheck} />
})}
</div>
)
}
const FruitOption = ({ fruit, handleCheck }) => {
console.log('** THIS RENDERS TWICE EVERY TIME USER SELECTS A FRUIT **')
return (
<div key={fruit.id}>
<input
checked={fruit.selected}
onChange={(e) => handleCheck(e.target.checked, fruit.id)}
type='checkbox'
/>
<label>{fruit.name}</label>
</div>
)
}
export default Example

首先,handleCheck函数有问题(但它与您询问的内容无关(。您的代码正在直接修改fruit对象(f.selected = bool(,但在React状态下不允许这样做,状态中的对象不能被直接修改,如果违反该规则,则渲染可能不正确。相反,您需要复制对象并修改副本(就像使用数组一样(:

const handleCheck = (bool, id) => {
setFruits((prev) => {
return prev.map((f) => {
if (f.id === id) {
return {...f, selected: bool}; // ***
}
return f;
});
});
};

但这不是你所问的,只是需要解决的其他问题。:-(

您看到console.loghandleCheck之后执行两次的原因是组件必须重新渲染(用于更改(,并且有两个可见的结果,因此您看到对FruitOption组件函数的两次调用。这有两个原因:

每次调用Example组件函数时,handleChange都会发生变化,因此FruitOption每次都会看到一个新的道具;和
  • 当道具不变时,FruitOption不会避免重新渲染,所以即使修复了#1,您仍然会看到两个console.log调用;和

  • 另外,FruitOption元素上没有key,这可能会导致渲染问题。渲染数组中的元素时,始终包含有意义的关键点。(不要只使用索引,这是有问题的;但是你的水果对象有一个id,这是完美的。(

    修复它:

    1. 记忆handleChange,这样它就不会每次都重新创建,可能是通过useCallback

    2. 使用React.memo,这样FruitOption在其道具不变的情况下就不会被调用(参见本答案末尾的class组件等效项(,并且

    3. Example中的FruitOption元素添加有意义的key

    将这些和上面的handleChange修复程序放在一起:

    const Example = () => {
    const [fruits, setFruits] = React.useState([
    { id: 1, name: 'apple', visible: true, selected: false },
    { id: 2, name: 'banana', visible: false, selected: false },
    { id: 3, name: 'orange', visible: true, selected: false }
    ]);
    const visibleFruits = React.useMemo(() => {
    return fruits.filter((f) => f.visible);
    }, [fruits]);
    const handleCheck = React.useCallback(
    (bool, id) => {
    setFruits((prev) => {
    return prev.map((f) => {
    if (f.id === id) {
    return {...f, selected: bool}; // ***
    }
    return f;
    });
    });
    },
    []  // *** No dependencies since all it uses is `setFruits`, which is
    // stable for the lifetime of the component
    );
    return (
    <div>
    {visibleFruits.map((fruit) => {
    // *** Note the key
    return <FruitOption key={fruit.id} fruit={fruit} handleCheck={handleCheck} />
    })}
    </div>
    );
    }
    // *** `React.memo` will compare the props and skip the call if they're the same, reusing
    // the previous call's result.
    const FruitOption = React.memo(({ fruit, handleCheck }) => {
    console.log(`Rendering fruit ${fruit.id}`);
    return (
    <div key={fruit.id}>
    <input
    checked={fruit.selected}
    onChange={(e) => handleCheck(e.target.checked, fruit.id)}
    type='checkbox'
    />
    <label>{fruit.name}</label>
    </div>
    );
    });
    ReactDOM.render(<Example />, document.getElementById("root"));
    <div id="root"></div>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react/17.0.2/umd/react.development.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/17.0.2/umd/react-dom.development.js"></script>

    正如您所看到的,在所有这些都到位的情况下,只有更改后的水果才会被重新渲染。

    ReReact.memo:对于具有更复杂需求的组件,您可以提供一个函数作为第二个参数,用于确定这两组道具是否是";相同的";用于渲染目的。默认情况下,React.memo只做一个浅相等比较,这通常就足够了。


    最后:对于class组件,不提供相等回调的React.memo等效于扩展PureComponent而不是Component。如果您想使对props的检查更加细粒度,您可以实现shouldComponentUpdate

    相关内容

    • 没有找到相关文章

    最新更新