React.useMemo 正在重新呈现数组中的所有项目
React.useMemo is re-rendering all items in array
我有一个 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
),但是您 not allowed to do that with React state,处于 状态的对象不能 直接修改,违反规则渲染可能不正确。相反,您需要复制对象并修改副本(就像您使用数组一样):
const handleCheck = (bool, id) => {
setFruits((prev) => {
return prev.map((f) => {
if (f.id === id) {
return {...f, selected: bool}; // ***
}
return f;
});
});
};
但这不是您要问的问题,只是需要解决的其他问题。 :-)
你看到console.log
在handleCheck
之后执行了两次的原因是组件必须重新渲染(为了改变),并且有两个可见的水果,所以你看到两个调用您的 FruitOption
组件函数。有两个原因:
handleChange
每次你的 Example
组件函数被调用时都会改变,所以 FruitOption
每次都会看到一个新的道具;和
FruitOption
在它的 props 没有改变时不会避免重新渲染,所以即使你修复了 #1,你仍然会看到两个 console.log
电话;和
另外,没有key
on the FruitOption
elements, which can cause rendering issues. Always include a meaningful key when rendering elements in an array. (Don't just use the index, it's problematic;但是你的水果对象有一个 id
,这是完美的。)
要修复它:
记忆 handleChange
这样它就不会每次都重新创建,可能是通过 useCallback
和
使用 React.memo
以便 FruitOption
在它的 props 没有改变时不会被调用(请参阅 class
组件的答案的结尾等效)和
在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>
如您所见,所有这些都就绪后,只有更改后的水果会重新渲染。
Re React.memo
: 对于要求更复杂的组件,你可以提供一个函数作为第二个参数,用于确定两组 props 是否“相同”以用于渲染目的。默认情况下,React.memo
只进行浅层相等比较,这通常就足够了。
最后:对于 class
组件,不提供相等回调的 React.memo
等效于扩展 PureComponent
instead of Component
. If you want to make your check of the props more fine-grained, you can implement shouldComponentUpdate
。
我有一个 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
),但是您 not allowed to do that with React state,处于 状态的对象不能 直接修改,违反规则渲染可能不正确。相反,您需要复制对象并修改副本(就像您使用数组一样):
const handleCheck = (bool, id) => {
setFruits((prev) => {
return prev.map((f) => {
if (f.id === id) {
return {...f, selected: bool}; // ***
}
return f;
});
});
};
但这不是您要问的问题,只是需要解决的其他问题。 :-)
你看到console.log
在handleCheck
之后执行了两次的原因是组件必须重新渲染(为了改变),并且有两个可见的水果,所以你看到两个调用您的 FruitOption
组件函数。有两个原因:
handleChange
每次你的Example
组件函数被调用时都会改变,所以FruitOption
每次都会看到一个新的道具;和FruitOption
在它的 props 没有改变时不会避免重新渲染,所以即使你修复了 #1,你仍然会看到两个console.log
电话;和
另外,没有key
on the FruitOption
elements, which can cause rendering issues. Always include a meaningful key when rendering elements in an array. (Don't just use the index, it's problematic;但是你的水果对象有一个 id
,这是完美的。)
要修复它:
记忆
handleChange
这样它就不会每次都重新创建,可能是通过useCallback
和使用
React.memo
以便FruitOption
在它的 props 没有改变时不会被调用(请参阅class
组件的答案的结尾等效)和在
中的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>
如您所见,所有这些都就绪后,只有更改后的水果会重新渲染。
Re React.memo
: 对于要求更复杂的组件,你可以提供一个函数作为第二个参数,用于确定两组 props 是否“相同”以用于渲染目的。默认情况下,React.memo
只进行浅层相等比较,这通常就足够了。
最后:对于 class
组件,不提供相等回调的 React.memo
等效于扩展 PureComponent
instead of Component
. If you want to make your check of the props more fine-grained, you can implement shouldComponentUpdate
。