[英]React Hook useEffect has a missing dependency: 'dispatch'
This is my first time working with react js, im trying to remove the alert when leaving this view cause i don't want to show it on the other view but in case that there is no error i want to keep the success alert to show it when i'm gonna redirect to the other view这是我第一次使用 React js,我试图在离开此视图时删除警报,因为我不想在另一个视图上显示它,但如果没有错误,我想保留成功警报以显示当我要重定向到另一个视图时
but im getting this wearning on google chrome Line 97:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
但我在 google chrome
Line 97:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
Line 97:6: React Hook useEffect has a missing dependency: 'dispatch'. Either include it or remove the dependency array react-hooks/exhaustive-deps
if i did include dispatch i get infinite loop如果我确实包括调度我得到无限循环
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" />;
}
this is useUserStore这是 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);
dispatch
comes from a custom hook
so it doesn't have an stable signature therefore will change on each render (reference equality). dispatch
来自自定义hook
,因此它没有稳定的签名,因此会在每次渲染时发生变化(引用相等)。 Add an aditional layer of dependencies by wrapping the handler inside an useCallback
hook通过将处理程序包装在
useCallback
挂钩中来添加额外的依赖层
const [foo, dispatch] = myCustomHook()
const stableDispatch = useCallback(dispatch, []) //assuming that it doesn't need to change
useEffect(() =>{
stableDispatch(foo)
},[stableDispatch])
useCallback
and useMemo
are helper hooks with the main purpose off adding an extra layer of dependency check to ensure synchronicity. useCallback
和useMemo
是辅助钩子,主要目的是添加额外的依赖检查层以确保同步性。 Usually you want to work with useCallback
to ensure a stable signature to a prop
that you know how will change and React doesn't.通常你想使用
useCallback
来确保一个你知道如何改变而 React 不会改变的prop
的稳定签名。
A function
(reference type) passed via props
for example例如,通过
props
传递的function
(参考类型)
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [])
}
Lets assume you have a child component which uppon mounting must set some state in the parent (not usual), the above code will generate a warning of undeclared dependency in useEffect
, so let's declare setParentState
as a dependency to be checked by React假设您有一个子组件,在安装时必须在父组件中设置一些 state(不常见),上面的代码将在
useEffect
中生成未声明依赖项的警告,所以让我们将setParentState
声明为依赖项以由 React 检查
const Component = ({ setParentState }) =>{
useEffect(() => setParentState('mounted'), [setParentState])
}
Now this effect runs on each render, not only on mounting, but on each update.现在,这种效果在每次渲染时都会运行,不仅在安装时,而且在每次更新时。 This happens because
setParentState
is a function
which is recreated every time the function Component
gets called.发生这种情况是因为
setParentState
是一个function
,每次调用 function Component
时都会重新创建它。 You know that setParentState
won't change it's signature overtime so it's safe to tell React that.你知道
setParentState
不会改变它的签名超时,所以告诉 React 是安全的。 By wrapping the original helper inside an useCallback
you're doing exactly that (adding another dependency check layer).通过将原始帮助程序包装在
useCallback
,您就可以做到这一点(添加另一个依赖检查层)。
const Component = ({ setParentState }) =>{
const stableSetter = useCallback(() => setParentState(), [])
useEffect(() => setParentState('mounted'), [stableSetter])
}
There you go.那里有 go。 Now
React
knows that stableSetter
won't change it's signature inside the lifecycle therefore the effect do not need too run unecessarily.现在
React
知道stableSetter
不会在生命周期内改变它的签名,因此效果不需要太不必要地运行。
On a side note useCallback
it's also used like useMemo
, to optmize expensive function calls (memoization).在旁注
useCallback
它也像useMemo
一样使用,以优化昂贵的 function 调用(记忆)。
The two mai/n purposes of useCallback
are useCallback
的两个主要目的是
Optimize child components that rely on reference equality to prevent unnecessary renders.优化依赖引用相等的子组件以防止不必要的渲染。 Font
字体
Memoize expensive calculations记住昂贵的计算
This solution is no longer needed on es-lint-plugin-react-hooks@4.1.0
and above. es-lint-plugin-react-hooks@4.1.0
及更高版本不再需要此解决方案。
Now useMemo
and useCallback
can safely receive referential types as dependencies.现在
useMemo
和useCallback
可以安全地接收引用类型作为依赖项。 #19590 #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} />
}
Here is another example of how to safely stabilize(normalize) a callback这是另一个如何安全地稳定(规范化)回调的示例
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>
)
}
Now referential types with stable signature such as those provenients from useState
or useDispatch
can safely be used inside an effect without triggering exhaustive-deps
even when coming from props
现在,具有稳定签名的引用类型(例如来自
useState
或useDispatch
的那些来源)可以安全地在效果内使用,即使来自props
也不会触发exhaustive-deps
I think you can solve the problem at the root but that means changing useCombinedReducers, I forked the repo and created a pull request because I don't think useCombinedReducers should return a new reference for dispatch every time you call it.我认为您可以从根本上解决问题,但这意味着更改 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;
Here is a working example:这是一个工作示例:
const reduceA = (state, { type }) => type === 'a'? { count: state.count + 1 }: state; const reduceC = (state, { type }) => type === 'c'? { count: state.count + 1 }: state; const state = { count: 1 }; function App() { const [a, b] = React.useReducer(reduceA, state); const [c, d] = React.useReducer(reduceC, state); //memoize what is passed to useCombineReducers const obj = React.useMemo( () => ({ a: [a, b], c: [c, d] }), [a, b, c, d] ); //does not do anything with reduced state const [, reRender] = React.useState(); const [s, dispatch] = useCombinedReducers(obj); const rendered = React.useRef(0); const [sc, setSc] = React.useState(0); const [dc, setDc] = React.useState(0); rendered.current++;//display how many times this is rendered React.useEffect(() => {//how many times state changed setSc(x => x + 1); }, [s]); React.useEffect(() => {//how many times dispatch changed setDc(x => x + 1); }, [dispatch]); return ( <div> <div>rendered {rendered.current} times</div> <div>state changed {sc} times</div> <div>dispatch changed {dc} times</div> <button type="button" onClick={() => reRender({})}> re render </button> <button type="button" onClick={() => dispatch({ type: 'a' })} > change a </button> <button type="button" onClick={() => dispatch({ type: 'c' })} > change c </button> <pre>{JSON.stringify(s, undefined, 2)}</pre> </div> ); } 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]; }; ReactDOM.render(<App />, document.getElementById('root'));
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/16.8.4/umd/react.production.min.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/react-dom/16.8.4/umd/react-dom.production.min.js"></script> <div id="root"></div>
Problem: You have this code and it creates an infinite loop.问题:你有这段代码,它创建了一个无限循环。 This is because
array
is in the dependency array, and you setArray
on every run of the useEffect
, which means it will continue to be set.这是因为
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>;
};
There are multiple solutions:有多种解决方案:
Solution #1: using the previous state解决方案#1:使用之前的 state
The setState
can also take a callback, which gives you the current state as an argument. setState
也可以接受一个回调,它给你当前的 state 作为参数。 This can sometimes be used to solve the problem.这有时可以用来解决问题。 Now you will not need to include
array
in the dependency array:现在您不需要在依赖数组中包含
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>;
};
Solution #2: running the useEffect conditionally解决方案#2:有条件地运行 useEffect
Sometimes you can find a condition to run the useEffect
.有时您可以找到运行
useEffect
的条件。 For example, we only need to set our array
when the array includes the removeValue
.例如,我们只需要在数组包含
removeValue
时设置我们的array
。 Therefore we can return early:因此我们可以提前返回:
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>;
};
Solution #3: using useCompare
解决方案#3:使用
useCompare
Sometimes the solution above is not possible due to restrictions, so here is a more sophisticated way.有时上面的解决方案由于限制是不可能的,所以这里有一个更复杂的方法。 This is for more complex problems, but I have yet to see a
useEffect
problem it cannot solve.这是针对更复杂的问题,但我还没有看到它无法解决的
useEffect
问题。 It requires two additional functions I will provide below.它需要我将在下面提供的两个附加功能。
I have commented on the code below to explain the function:我对下面的代码进行了评论,以解释 function:
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>;
};
Solution #4: disabling the error (not recommended)`解决方案#4:禁用错误(不推荐)`
The last "solution" is avoiding the problem.最后一个“解决方案”是避免问题。 In some cases, this is enough and will work perfectly, but might cause debugging problems if the
useEffect
changes later, or if not used correctly.在某些情况下,这已经足够并且可以完美地工作,但如果
useEffect
稍后更改或未正确使用,则可能会导致调试问题。
In this example, say you know that you will never need to run the useEffect
when array changes, you can just remove it from the dependency array and ignore the error:在此示例中,假设您知道当数组更改时您将永远不需要运行
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);
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [removeValue]);
return <div>{array.join(" ")}</div>;
};
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.