React Hook useEffect 缺少依赖项:'dispatch'

IT技术 reactjs react-hooks
2021-04-19 13:19:56

这是我第一次使用 react js,我试图在离开此视图时删除警报,因为我不想在另一个视图上显示它,但如果没有错误,我想保持成功警报显示当我要重定向到另一个视图时

但我在谷歌浏览器上穿上了这件衣服 Line 97:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps

如果我确实包含了调度,我会得到无限循环

const [state, dispatch] = useUserStore();
useEffect(() => {
    let token = params.params.token;
    checktoken(token, dispatch);
  }, [params.params.token]);

  useEffect(() => {
    return () => {
      if (state.alert.msg === "Error") {
        dispatch({
          type: REMOVE_ALERT
        });
      }
    };
  }, [state.alert.msg]);

//response from the api
if (!token_valide || token_valide_message === "done") {
      return <Redirect to="/login" />;
    }

这是 useUserStore

  const globalReducers = useCombinedReducers({
    alert: useReducer(alertReducer, alertInitState),
    auth: useReducer(authReducer, authInitState),
    register: useReducer(registerReducer, registerInitState),
    token: useReducer(passeditReducer, tokenvalidationInitState)
  });
  return (
    <appStore.Provider value={globalReducers}>{children}</appStore.Provider>
  );
};

export const useUserStore = () => useContext(appStore);
3个回答

更新 09/11/2020

不再需要此解决方案es-lint-plugin-react-hooks@4.1.0

现在useMemo并且useCallback可以安全地接收引用类型作为依赖项。#19590

function MyComponent() {
  const foo = ['a', 'b', 'c']; // <== This array is reconstructed each render
  const normalizedFoo = useMemo(() => foo.map(expensiveMapper), [foo]);
  return <OtherComponent foo={normalizedFoo} />
}

这是如何安全地稳定(标准化)回调的另一个示例

const Parent = () => {
    const [message, setMessage] = useState('Greetings!')

    return (
        <h3>
            { message }
        </h3>
        <Child setter={setMessage} />
    )
}

const Child = ({
    setter
}) => {
    const stableSetter = useCallback(args => {
        console.log('Only firing on mount!')
        return setter(args)
    }, [setter])

    useEffect(() => {
        stableSetter('Greetings from child\'s mount cycle')
    }, [stableSetter]) //now shut up eslint

    const [count, setCount] = useState(0)

    const add = () => setCount(c => c + 1)

    return (
        <button onClick={add}>
            Rerender {count}
        </button>
    )
}

现在具有稳定签名的引用类型,例如那些来自useStateuseDispatch可以安全地在效果内使用,exhaustive-deps即使来自效果也不会触发props

编辑silly-andras-9v1yp

---

旧答案

dispatch来自自定义,hook因此它没有稳定的签名,因此会在每次渲染时更改(引用相等)。通过将处理程序包装在useCallback钩子中来添加额外的依赖层

   const [foo, dispatch] = myCustomHook()
  
   const stableDispatch = useCallback(dispatch, []) //assuming that it doesn't need to change

   useEffect(() =>{
        stableDispatch(foo)
   },[stableDispatch])

useCallback并且useMemo是辅助钩子,主要目的是添加额外的依赖检查以确保同步。通常你想useCallback确保一个稳定的签名prop,你知道会如何改变而 React 不会。

A function(引用类型)通过props例如

const Component = ({ setParentState }) =>{
    useEffect(() => setParentState('mounted'), [])
}

让我们假设你有一个子组件,它在安装时必须在父组件中设置一些状态(不常见),上面的代码会在 中生成未声明依赖项的警告useEffect,所以让我们声明setParentState为要由 React 检查的依赖项

const Component = ({ setParentState }) =>{
    useEffect(() => setParentState('mounted'), [setParentState])
}

现在这个效果在每次渲染上运行,不仅在安装时,而且在每次更新时。发生这种情况是因为每次调用函数时都会重新创建setParentStateis a 你知道这不会改变它的签名超时所以告诉 React 是安全的。通过将原始助手包装在您正在做的事情中(添加另一个依赖项检查层)。functionComponentsetParentStateuseCallback

const Component = ({ setParentState }) =>{
   const stableSetter = useCallback(() => setParentState(), [])

   useEffect(() => setParentState('mounted'), [stableSetter])
}

你去吧。现在React知道stableSetter在生命周期内不会改变它的签名,因此效果不需要太不必要地运行。

附带说明useCallback一下,它也用于useMemo优化昂贵的函数调用(记忆)。

的两个主要目的useCallback

  • 优化依赖引用相等的子组件以防止不必要的渲染。字体

  • 记住昂贵的计算

即使他dispatch用一个useCallbackinside包装customHook问题也会持续存在,并且useUserStore不会被视为他实际上可以更改源代码的钩子。
2021-05-22 13:19:56
我也不知道。如果他能确定这是一个比useCallback每次都包在里面更好的解决方案
2021-05-25 13:19:56
会起作用,但我认为 OP 应该在源头解决它,而不是 OP 将在许多地方使用调度的地方。
2021-05-26 13:19:56
我不知道 useCombinedReducers 库,但它可能被修复 useCombinedReducers(not passing new ref every time)
2021-05-28 13:19:56
@Dupocas 你的解决方案对我来说很好,谢谢你的帮助,我实际上没有使用 redux 我是这个框架的新手
2021-06-06 13:19:56

我认为您可以从根本上解决问题,但这意味着更改 useCombinedReducers,我分叉了 repo 并创建了一个拉取请求,因为我认为 useCombinedReducers 不应该在每次调用它时返回一个新的调度引用。

function memoize(fn) {
  let lastResult,
    //initial last arguments is not going to be the same
    //  as anything you will pass to the function the first time
    lastArguments = [{}];
  return (...currentArgs) => {
    //returning memoized function
    //check if currently passed arguments are the same as
    //  arguments passed last time
    const sameArgs =
      currentArgs.length === lastArguments.length &&
      lastArguments.reduce(
        (result, lastArg, index) =>
          result && Object.is(lastArg, currentArgs[index]),
        true,
      );
    if (sameArgs) {
      //current arguments are same as last so just
      //  return the last result and don't execute function
      return lastResult;
    }
    //current arguments are not the same as last time
    //  or function called for the first time, execute the
    //  function and set last result
    lastResult = fn.apply(null, currentArgs);
    //set last args to current args
    lastArguments = currentArgs;
    //return result
    return lastResult;
  };
}

const createDispatch = memoize((...dispatchers) => action =>
  dispatchers.forEach(fn => fn(action)),
);
const createState = memoize(combinedReducers =>
  Object.keys(combinedReducers).reduce(
    (acc, key) => ({ ...acc, [key]: combinedReducers[key][0] }),
    {},
  ),
);
const useCombinedReducers = combinedReducers => {
  // Global State
  const state = createState(combinedReducers);

  const dispatchers = Object.values(combinedReducers).map(
    ([, dispatch]) => dispatch,
  );

  // Global Dispatch Function
  const dispatch = createDispatch(...dispatchers);

  return [state, dispatch];
};

export default useCombinedReducers;

这是一个工作示例:

非常感谢你的帮助,我要去试试
2021-06-02 13:19:56

问题的最小示例,2021

问题:你有这个代码,它创建了一个无限循环。这是因为array是在依赖数组中,并且您setArray在每次运行 时useEffect,这意味着它将继续被设置。

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);
  }, [array, removeValue]);

  return <div>{array.join(" ")}</div>;
};

有多种解决方案:

解决方案#1:使用之前的状态

setState也可以采取一个回调,它给你的当前状态作为参数。这有时可以用来解决问题。现在你不需要包含array在依赖数组中:

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    setArray((previousArray) => {
      const newArray = previousArray.filter((value) => value !== removeValue);
      return newArray;
    });
  }, [removeValue]);

  return <div>{array.join(" ")}</div>;
};

解决方案#2:有条件地运行 useEffect

有时您可以找到运行useEffect. 例如,我们只需要array在数组包含removeValue. 因此我们可以早点返回:

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    const containsValue = array.includes(removeValue);
    if (!containsValue) return;

    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);
  }, [array, removeValue]);

  return <div>{array.join(" ")}</div>;
};

解决方案#3:使用 useCompare

有时由于限制,上述解决方案是不可能的,所以这里有一个更复杂的方法。这是针对更复杂的问题,但我还没有看到useEffect它无法解决的问题。它需要我将在下面提供的两个附加功能。

我对下面的代码进行了评论以解释该功能:

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useCompare will return either `true` or `false` depending
  // on if the value has changed. In this example, `useEffect` will
  // rerun every time the array length changes.
  // This can be applied to any other logic such as if removeValue
  // changes, depending on when you want to run the `useEffect`
  const arrayLengthChanged = useCompare(array.length);
  useEffect(() => {
    if (!arrayLengthChanged) return;
    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);
  }, [array, arrayLengthChanged, removeValue]);

  return <div>{array.join(" ")}</div>;
};

解决方案#4:禁用错误(不推荐)`

最后一个“解决方案”是避免这个问题。在某些情况下,这已经足够了,并且可以完美运行,但如果useEffect稍后进行更改或未正确使用,则可能会导致调试问题

在此示例中,假设您知道永远不需要运行useEffectwhen 数组更改,您只需将其从依赖项数组中删除并忽略错误:

const MyComponent = ({ removeValue }) => {
  const [array, setArray] = useState([1, 2, 3, 4, 5]);

  // useEffect to remove `removeValue` from array
  useEffect(() => {
    const newArray = array.filter((value) => value !== removeValue);
    setArray(newArray);

    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [removeValue]);

  return <div>{array.join(" ")}</div>;
};