useEffect 与 useContext 陷入无限循环

useEffect stuck in infinite loop with useContext

我有一个功能组件 Pets 可以显示当前登录用户的所有宠物。在我的 Pets 组件中,我使用 useEffectuseState 来实现这一点。

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% 确定它在做什么,否则我会进一步建议您。

无限循环从您的上下文开始。

  1. 您的上下文值会为每次渲染创建。
  2. 因为你的context值变了,效果调用fetch
  3. 获取更新状态,继续触发渲染。因为渲染被触发,第一个会被触发。

修复它:

  // 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]);

https://reactjs.org/docs/hooks-reference.html#usecallback