【问题标题】:Incorrect dependency behavior in hook useEffect?钩子useEffect中的依赖行为不正确?
【发布时间】:2019-10-20 17:34:53
【问题描述】:

useEffect 内部存在无限循环问题。我只想在 id 依赖项发生变化时发出新请求。当我不传递数据时,我遇到了setData 的问题。我的数据状态没有更新。当我将数据传递给依赖项时,我有一个无限循环。如何解决它以及为什么?

import React, {useEffect, useState} from 'react';
import LeftArrow from './LeftArrow';
import RightArrow from './RightArrow';
import SlideItem from './SlideItem';

const Slide = () => {
    const [id, setId] = useState(0);
    const [data, setData] = useState({currencies:[], isFetching:false});

    const images = [
        "https://images.pexels.com/photos/672532/pexels-photo-672532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
        "https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
        "https://images.pexels.com/photos/64271/queen-of-liberty-statue-of-liberty-new-york-liberty-statue-64271.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
    ];

    useEffect(()=> {
        const getCurrentCurrency = async () => {
            try{
                setData({currencies: data.currencies, isFetching: true});
                console.log("data", data.isFetching);
                const currencyArr = [];
                const response = await fetch(`https://api.exchangeratesapi.io/latest?base=GBP`);
                const responseData  = await response.json();
                console.log(responseData);
                const {EUR:euro ,CHF:franc, USD: dolar} = responseData.rates;
                currencyArr.push(euro,franc,dolar);
                console.log(currencyArr);
                setData({currencies: currencyArr, isFetching: false});
                console.log("currencies", data);     
            }
            catch (e) {
                console.log(e);
                setData({currencies: data.currencies, isFetching: false});
            }
        };
        getCurrentCurrency();
    }, [id]);



    const goToPrevSlide = () => {
      //  id === 0 ? setId(2) : setId(id-1);
    }
    const goToNextSlide = () =>{
      //  id === 2 ? setId(0) : setId(id+1);
    }


    return(

        <div className="slide">
            <div className="slide-wrapper"
                style={{
                    transform: `translateX(500px)`,
                    transition: 'transform ease-out 0.45s'
                  }}
            >
             {
                 currencies.map((currency, i, images) => (
                  <SlideItem currency={currency} key={i} imageUrl={images[i]} />
             ))
            }
            </div>
            <LeftArrow 
                 goToPrevSlide={goToPrevSlide}
            />
            <RightArrow 
                 goToNextSlide={goToNextSlide}    
            />  
        </div>
    );
}

export default Slide;

【问题讨论】:

  • 糟糕的解决方案是使用useEventCallback,但即使是模式来自的 React 文档也表示这不是一个好主意。您可以尝试使用 useReducer 和 dispatch the data as an action 设置您的状态
  • 你能用包括退货在内的整个组件来编辑你的帖子吗?您提供的代码在我的本地环境中没有错误
  • @WilliamWillman 当您满足 linter 并将 data.currencies 添加到效果依赖项时不会。

标签: javascript reactjs asynchronous react-hooks


【解决方案1】:

在这里,即使您使用自己的值设置状态,当 useEffect 完成时它认为您已经使用新值更新了状态,因为 data.currencies 将在 useEffect 期间设置,这会在将其添加为时导致循环依赖关系。

如果您想保留代码原样,可以在前面的行中添加// eslint-disable-next-line。

原因:在这种情况下,您知道您不想在 data.currencies 更改时运行此 useEffect(因为这是此 useEffect 的重点)

另一种选择,因为您实际上是在尝试将货币更改为加载状态,您可以简单地更改:

setData({currencies: data.currencies, isFetching: true});
-and-
setData({currencies: data.currencies, isFetching: false});

成为:

setData({isFetching: true});
-and-
setData({isFetching: false});

(请记住,setState 不是即时的,因为设置状态会进入 javascript 队列,因此最好不要依赖它)

完整(稍作修改的返回语句)工作代码:

import React, {useEffect, useState} from 'react';
import ReactDOM from 'react-dom'

const Slide = () => {
    const [id, setId] = useState(0);
    const [data, setData] = useState({currencies:[], isFetching:false});

    const images = [
       "https://images.pexels.com/photos/672532/pexels-photo-672532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
       "https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940",
       "https://images.pexels.com/photos/64271/queen-of-liberty-statue-of-liberty-new-york-liberty-statue-64271.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940"
    ];

    useEffect(()=> {
        const getCurrentCurrency = async () => {
            try{
                setData({isFetching: true});
                const currencyArr = [];
                const response = await fetch(`https://api.exchangeratesapi.io/latest?base=GBP`);
                const responseData  = await response.json();
                const {EUR:euro ,CHF:franc, USD: dolar} = responseData.rates;
                currencyArr.push(euro,franc,dolar);
                setData({currencies: currencyArr, isFetching: false});
            }
            catch (e) {
                setData({isFetching: false});
            }
        };
        getCurrentCurrency();
    }, [id]);



    const goToPrevSlide = () => {
         id === 0 ? setId(2) : setId(id-1);
    }
    const goToNextSlide = () =>{
         id === 2 ? setId(0) : setId(id+1);
    }


    return(
        <div>
            <div className="slide">
                <div>
                    {id}
                </div>
                {JSON.stringify(data)}
                <div>
                    <button onClick={goToPrevSlide}>Prev</button>
                    <button onClick={goToNextSlide}>Next</button>  
                </div>
            </div>
        </div>
    );
}

【讨论】:

    【解决方案2】:

    这是一个如何使用 useDispatch 执行此操作的示例,它包含更多代码,但您可以将减速器从组件中取出并放在单独的文件中,并且也应该将动作类型和动作创建者放在单独的文件中。

    const Slide = () => {
      const [id, setId] = React.useState(0);
      const [data, dispatch] = React.useReducer(
        //you can move this function to a separate file and import it
        (state, action) => {
          const { type } = action;
          if (type === 'LOAD') {//action types should be defined as constants
            return {
              ...state,
              loading: true,
            };
          }
          if (type === 'RECEIVED') {
            const currencies = action.payload;
            return {
              currencies,
              loading: false,
            };
          }
          if (type === 'ERROR') {
            return {
              ...state,
              loading: false,
            };
          }
          return state;
        }
      );
    
      const images = [
        'https://images.pexels.com/photos/672532/pexels-photo-672532.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
        'https://images.pexels.com/photos/773471/pexels-photo-773471.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
        'https://images.pexels.com/photos/64271/queen-of-liberty-statue-of-liberty-new-york-liberty-statue-64271.jpeg?auto=compress&cs=tinysrgb&dpr=2&h=650&w=940',
      ];
    
      useEffect(() => {
        const getCurrentCurrency = async () => {
          try {
            //should create action creator functions so you can do
            //dispatch(load()) put the action creators in separate file
            //usually called actions.js
            dispatch({ type: 'LOAD' });
            console.log('loading');
            //not sure how this depends on id since you are not using id
            //  in the request or anywhere in the effect
            const response = await fetch(
              `https://api.exchangeratesapi.io/latest?base=GBP`
            );
            const responseData = await response.json();
            console.log(responseData);
            const {
              EUR: euro,
              CHF: franc,
              USD: dollar,
            } = responseData.rates;
            const currencies = [euro, franc, dollar];
            console.log(currencies);
            dispatch({
              type: 'RECEIVED',
              payload: currencies,
            });
            console.log('currencies', currencies);
          } catch (e) {
            console.log(e);
            dispatch({
              type: 'ERROR',
            });
          }
        };
        getCurrentCurrency();
      }, [id]);
    
      //other stuff and return jsx
    
    };
    

    在你发送之前你应该检查组件是否是still mounted,如果组件不会卸载那么这不是问题。

    【讨论】:

      猜你喜欢
      • 2020-01-23
      • 2019-10-08
      • 2021-01-28
      • 2020-09-15
      • 2019-08-06
      • 1970-01-01
      • 1970-01-01
      • 2020-06-03
      • 2021-11-16
      相关资源
      最近更新 更多