如何读取和修改 useEffect IAW 'exhaustive-deps' lint 规则中的状态

IT技术 reactjs react-hooks
2021-05-01 01:22:33

我有一张地图,上面有一个叠加层。我有一个效果,当基础数据发生变化时,删除旧的覆盖层并重新绘制一个新的覆盖层。

值得注意的是,我正在使用"react-hooks/exhaustive-deps",并且从我读到的所有内容看来,仅仅删除依赖项overlay并不是正确的答案(但它确实有效)。

值得注意的是,数据是传入的props。

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 规则,这对他们来说不是一个因素,因为他们在更改状态之前没有读取状态。


编辑:

我仍然遇到 useState 和 reducer 应该是纯的问题。我对 ( 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]);
4个回答

您可以使用内部的函数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在这种影响之外是否会发生变化?IE 您的组件是否会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]);

}

请注意,我已将 dispatch 添加到依赖项数组中,这是一种很好的做法,您永远不应该对依赖项撒谎!. 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 规则的情况下实现您想要做的事情。