我有一张有叠加层的地图。我有一个效果,当基础数据更改时,删除旧覆盖并重新绘制新覆盖。
值得注意的是,我正在使用"react-hooks/exhaustive-deps"
,从我读到的所有内容来看,仅仅删除依赖项overlay
并不是正确的答案(但它确实有效(。
值得注意的是,数据是传入的道具。
useEffect(() => {
// remove the overlay if it is there,
// I should have overlay as a dependency per exhaustive-reps react eslint rule
if (overlay) map.removeOverlays(overlay);
// Generate the new overlay based on the new data (useCallback function)
const newOverlay = generateNewOverlay(data)
// Store the new overlay so I can remove it later if the data changes
// Doesn't need to be done right away though, just before next render
setOverlay(newOverlay);
// Show the new overlay on my map
map.addOverlays(newOverlay);
}, [map, overlay, data, generateNewOverlay]);
这当然将是一个无限循环,因为我正在设置覆盖并使其成为依赖项。我也不喜欢在效果中立即使用 setState,因为它会导致另一个渲染。我错过了什么?如何实现此逻辑?
我确实读了大约 5 个标题相似的问题,但它们没有回答我的问题。
类似的问题,但不问,同时保持详尽的代表deps lint规则,这对他们来说不是一个因素,因为他们在更改状态之前没有阅读状态。
编辑:
我仍然有问题,使用状态和减速器应该是纯粹的。我遇到问题的依赖项的目的是(overlay
(是我必须检查是否存在覆盖。如果是这样,我需要做一件非纯的事情,即在设置新叠加层之前从地图中删除该叠加层。这行代码使它不纯粹:
if (overlay) map.removeOverlays(overlay);
如果没有这一行,我永远不需要在我的依赖项列表中overlay
,整个事情也不会成为一个因素。如您所见,由于那条线,接受的答案并不纯粹。
useReducer 答案在化简器之外有这条线,所以overlay
应该是依赖项,或者它需要进入化简器。第一种选择是我试图通过"react-hooks/exhaustive-deps"
避免的,第二个答案并不纯粹。
感谢任何帮助。
最终编辑:
我发现解决这个问题的最佳方法是正确使用useEffect中的清理。我需要状态,以便以后可以从地图中删除内容。
useEffect(() => {
// remove the overlay if it is there,
// I originally needed this to store the overlay so I could remove it later, but if I use cleanup properly this isn't needed
// if (overlay) map.removeOverlays(overlay);
// Generate the new overlay based on the new data (useCallback function)
const newOverlay = generateNewOverlay(data)
// Store the new overlay so I can remove it later if the data changes
// Doesn't need to be done right away though, just before next render (This is what a cleanup is!)
// setOverlay(newOverlay);
// Show the new overlay on my map
map.addOverlays(newOverlay);
// New code below
return () => {map.removeOverlays(newOverlay)};
}, [map, data, generateNewOverlay]);
您可以在setOverlay
中使用一个函数:
useEffect(() => {
setOverlay(prev => {
// remove the overlay if it is there,
// I should have overlay as a dependency per exhaustive-reps react eslint rule
if (prev) map.removeOverlays(prev);
// Generate the new overlay based on the new data (useCallback function)
const newOverlay = generateNewOverlay(data)
// Show the new overlay on my map
map.addOverlays(newOverlay);
return newOverlay;
});
}, [map, data, generateNewOverlay]);
在prev
参数中,您可以获得overlay
的当前状态。这样,您就不必将overlay
状态变量添加到依赖项数组中。因此,overlay
更新时不会触发效果。
我建议简单地删除它并添加带有注释的忽略规则。这就是我让我的团队做的事情,也是 Gaearon 针对特定用例的建议。
注意:我确实喜欢@DamienFlury的解决方案,如果它有效(我怀疑它确实如此(。
useEffect(() => {
// remove the overlay if it is there,
if (overlay) map.removeOverlays(overlay);
// Generate the new overlay based on the new data (useCallback function)
const newOverlay = generateNewOverlay(data)
// Store the new overlay so I can remove it later if the data changes
// Doesn't need to be done right away though, just before next render
setOverlay(newOverlay);
// Show the new overlay on my map
map.addOverlays(newOverlay);
// value of overlay changing should not trigger effect
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [map, data, generateNewOverlay]);
这将扼杀警告,但如果效果的 API 发生变化,也会掩盖任何未来的依赖项更改,因此您基本上只能靠自己来确保效果在您预期时仍然触发。
overlay
会不会在这种效果之外发生变化? 也就是说,你的组件是否曾经在效果之外调用setOverlay
?
为了添加到@DamienFlury的解决方案中,您可以使用另一种技术从依赖项数组中删除不断变化的依赖项是使用 useReducer 钩子。这允许您将状态变量与效果分离
function reducer(state, action){
if(action.type === 'UPDATE_OVERLAY'){
return {
...state,
overlay: action.newOverlay, //update overlay on your state object
}
} else {
return state;
}
}
function YourComponent(yourProps){
const [ state, dispatch ] = useReducer(reducer, initialState);
// Other functions
useEffect(() => {
// remove the overlay if it is there,
if (overlay) map.removeOverlays(overlay);
// Generate the new overlay based on the new data (useCallback function)
const newOverlay = generateNewOverlay(data)
// this also allows you to decouple your state changes from your effects!
dispatch({
type: 'UPDATE_OVERLAY',
newOverlay,
});
// Show the new overlay on my map
map.addOverlays(newOverlay);
}, [dispatch, map, data, generateNewOverlay]);
}
请注意,我已经将调度添加到依赖项数组中只是作为一种很好的做法,您永远不应该对依赖项撒谎!React 保证调度函数在整个组件生命周期中保持不变。
我认为这里的问题是你适应这个 linter 规则的方法太被动了。每当useEffect
回调运行时,您都会运行它中的所有代码,但您实际上只想在data
更改时执行这些内容。
因此,您可以在代码中反映这一点:
const [shouldUpdateOverlay, setShouldUpdateOverlay] = useState(false);
useEffect(() => {
if (shouldUpdateOverlay) {
// remove the overlay if it is there,
// I should have overlay as a dependency per exhaustive-reps react eslint rule
if (overlay) map.removeOverlays(overlay);
// Generate the new overlay based on the new data (useCallback function)
const newOverlay = generateNewOverlay(data);
// Show the new overlay on my map
map.addOverlays(newOverlay);
// Store the new overlay so I can remove it later if the data changes
// Doesn't need to be done right away though, just before next render
setOverlay(newOverlay);
setShouldUpdateOverlay(false);
}
}, [map, overlay, shouldUpdateOverlay data, generateNewOverlay]);
// use from event handlers, etc.
const updateData = (newData) => {
setData(newData);
setShouldUpdateOverlay(true);
};
实际上,这是一种过度紧张的方法,可以通过将[data]
作为useEffect
中唯一的依赖项来实现相同的目标,因为这确实是唯一重要的依赖项。但是,我相信以上应该可以让您在不违反 linter 规则的情况下实现您想要做的事情。
如果要将旧的overlay
值保留在某个状态中以便以后将其删除,则无需使用状态。 它可以保存在 ref 上,然后在更改时不会触发重新渲染,从而避免无限循环。
refs 可用于这种目的(当您只想保存某些内容而不触发重新渲染时(: https://reactjs.org/docs/hooks-faq.html#is-there-something-like-instance-variables