useEffect 与 useContext 陷入无限循环
useEffect stuck in infinite loop with useContext
我有一个功能组件 Pets
可以显示当前登录用户的所有宠物。在我的 Pets
组件中,我使用 useEffect
和 useState
来实现这一点。
const [pets, setPets] = useState([]);
useEffect(() => {
const fetchPets = async () => {
try {
const { data } = await axios.get('/pet/mypets');
setPets(data.pets);
} catch (error) {
console.log(error);
}
};
fetchPets();
}, []);
然后我逐行渲染 table 中的所有宠物。问题是,当我尝试使用 useContext
执行此操作时,它最终陷入无限循环。例如,我有一个名为 petsContext
的文件,其中包含以下代码...
import React from 'react';
const PetsContext = React.createContext({
pets: [],
onRegister: (first_name, last_name, breed, age, weight) => {},
onUpdate: (first_name, last_name, breed, age, weight) => {},
onDelete: () => {},
onFetch: () => {},
});
export default PetsContext;
我有一个 petsProvider
文件,其中包含以下...
import React, { useReducer } from 'react';
import PetContext from './pets-context';
import axios from 'axios';
const initialState = {
pets: [],
message: '',
messageType: '',
};
const petReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_PETS_SUCCESS':
return {
...state,
pets: action.payload,
};
case 'REGISTER_PET_SUCCESS':
if (state.pets === undefined) {
state.pets = [];
}
return {
...state,
pets: [action.payload.pet, ...state.pets],
message: action.payload.message,
};
case 'REGISTER_PET_FAIL':
return {
...state,
message: 'Unable to register pet!',
};
default:
return initialState;
}
};
const PetProvider = (props) => {
const [petState, dispatchPetAction] = useReducer(petReducer, {
petReducer,
initialState,
});
const registerHandler = async (first_name, last_name, breed, age, weight) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
age = parseInt(age);
weight = parseFloat(weight);
const body = JSON.stringify({ first_name, last_name, breed, age, weight });
try {
const { data } = await axios.post('/pet/register', body, config);
dispatchPetAction({ type: 'REGISTER_PET_SUCCESS', payload: data });
} catch (error) {
console.log(error.response);
// dispatchPetAction({
// type: 'REGISTER_PET_FAIL',
// payload: {
// message: error.response.data.message,
// messageType: 'danger',
// },
// });
}
};
const fetchHandler = async () => {
try {
const { data } = await axios.get('/pet/mypets');
dispatchPetAction({ type: 'FETCH_PETS_SUCCESS', payload: data.pets });
} catch (err) {
console.log(err);
}
};
return (
<PetContext.Provider
value={{
pets: petState.pets,
onRegister: registerHandler,
onFetch: fetchHandler,
}}
>
{props.children}
</PetContext.Provider>
);
};
export default PetProvider;
并且在我的 Pets
组件中,而不是在我有以下内容之前显示的内容...
const petsContext = useContext(PetsContext);
useEffect(() => {
petsContext.onFetch();
}, [petsContext]);
// now I want to just access my pets by `petsContext.pets`
// which works but ends up in an infinite loop and I am not sure why.
我该如何修复这个无限循环以及为什么会发生这种情况?
useEffect
无限渲染的问题是因为依赖数组。如果您正在更新效果内部的状态,但效果取决于您正在更新的状态,那么效果会在您 petsContext
发生任何变化时调用。
这意味着当上下文改变时调用效果,然后它改变上下文,它再次调用自己无限直到你得到堆栈溢出 (rimshot).
要解决这个问题,请确定您希望此效果依赖于什么,并将其最小化为仅该对象。 onFetch
在您的示例中被声明为空函数,因此我不能 100% 确定它在做什么,否则我会进一步建议您。
无限循环从您的上下文开始。
- 您的上下文值会为每次渲染创建。
- 因为你的context值变了,效果调用fetch
- 获取更新状态,继续触发渲染。因为渲染被触发,第一个会被触发。
修复它:
// wrap fetchHandler in useCallback
const fetchHandler = useCallback(async () => {
try {
const { data } = await axios.get('/pet/mypets');
dispatchPetAction({ type: 'FETCH_PETS_SUCCESS', payload: data.pets });
} catch (err) {
console.log(err);
}
}, [dispatchPetAction]);
const { onFetch } = useContext(PetsContext);
// this effect should depend on onFetch, not petsContext
// now, it will only being call if dispatchPetAction is changed
// dispatchPetAction -> fetchHandler -> useEffect
useEffect(() => {
onFetch();
}, [onFetch]);
我有一个功能组件 Pets
可以显示当前登录用户的所有宠物。在我的 Pets
组件中,我使用 useEffect
和 useState
来实现这一点。
const [pets, setPets] = useState([]);
useEffect(() => {
const fetchPets = async () => {
try {
const { data } = await axios.get('/pet/mypets');
setPets(data.pets);
} catch (error) {
console.log(error);
}
};
fetchPets();
}, []);
然后我逐行渲染 table 中的所有宠物。问题是,当我尝试使用 useContext
执行此操作时,它最终陷入无限循环。例如,我有一个名为 petsContext
的文件,其中包含以下代码...
import React from 'react';
const PetsContext = React.createContext({
pets: [],
onRegister: (first_name, last_name, breed, age, weight) => {},
onUpdate: (first_name, last_name, breed, age, weight) => {},
onDelete: () => {},
onFetch: () => {},
});
export default PetsContext;
我有一个 petsProvider
文件,其中包含以下...
import React, { useReducer } from 'react';
import PetContext from './pets-context';
import axios from 'axios';
const initialState = {
pets: [],
message: '',
messageType: '',
};
const petReducer = (state = initialState, action) => {
switch (action.type) {
case 'FETCH_PETS_SUCCESS':
return {
...state,
pets: action.payload,
};
case 'REGISTER_PET_SUCCESS':
if (state.pets === undefined) {
state.pets = [];
}
return {
...state,
pets: [action.payload.pet, ...state.pets],
message: action.payload.message,
};
case 'REGISTER_PET_FAIL':
return {
...state,
message: 'Unable to register pet!',
};
default:
return initialState;
}
};
const PetProvider = (props) => {
const [petState, dispatchPetAction] = useReducer(petReducer, {
petReducer,
initialState,
});
const registerHandler = async (first_name, last_name, breed, age, weight) => {
const config = {
headers: {
'Content-Type': 'application/json',
},
};
age = parseInt(age);
weight = parseFloat(weight);
const body = JSON.stringify({ first_name, last_name, breed, age, weight });
try {
const { data } = await axios.post('/pet/register', body, config);
dispatchPetAction({ type: 'REGISTER_PET_SUCCESS', payload: data });
} catch (error) {
console.log(error.response);
// dispatchPetAction({
// type: 'REGISTER_PET_FAIL',
// payload: {
// message: error.response.data.message,
// messageType: 'danger',
// },
// });
}
};
const fetchHandler = async () => {
try {
const { data } = await axios.get('/pet/mypets');
dispatchPetAction({ type: 'FETCH_PETS_SUCCESS', payload: data.pets });
} catch (err) {
console.log(err);
}
};
return (
<PetContext.Provider
value={{
pets: petState.pets,
onRegister: registerHandler,
onFetch: fetchHandler,
}}
>
{props.children}
</PetContext.Provider>
);
};
export default PetProvider;
并且在我的 Pets
组件中,而不是在我有以下内容之前显示的内容...
const petsContext = useContext(PetsContext);
useEffect(() => {
petsContext.onFetch();
}, [petsContext]);
// now I want to just access my pets by `petsContext.pets`
// which works but ends up in an infinite loop and I am not sure why.
我该如何修复这个无限循环以及为什么会发生这种情况?
useEffect
无限渲染的问题是因为依赖数组。如果您正在更新效果内部的状态,但效果取决于您正在更新的状态,那么效果会在您 petsContext
发生任何变化时调用。
这意味着当上下文改变时调用效果,然后它改变上下文,它再次调用自己无限直到你得到堆栈溢出 (rimshot).
要解决这个问题,请确定您希望此效果依赖于什么,并将其最小化为仅该对象。 onFetch
在您的示例中被声明为空函数,因此我不能 100% 确定它在做什么,否则我会进一步建议您。
无限循环从您的上下文开始。
- 您的上下文值会为每次渲染创建。
- 因为你的context值变了,效果调用fetch
- 获取更新状态,继续触发渲染。因为渲染被触发,第一个会被触发。
修复它:
// wrap fetchHandler in useCallback
const fetchHandler = useCallback(async () => {
try {
const { data } = await axios.get('/pet/mypets');
dispatchPetAction({ type: 'FETCH_PETS_SUCCESS', payload: data.pets });
} catch (err) {
console.log(err);
}
}, [dispatchPetAction]);
const { onFetch } = useContext(PetsContext);
// this effect should depend on onFetch, not petsContext
// now, it will only being call if dispatchPetAction is changed
// dispatchPetAction -> fetchHandler -> useEffect
useEffect(() => {
onFetch();
}, [onFetch]);