【问题标题】:Why is my redux app not caching an async api call in redux-thunk?为什么我的 redux 应用程序没有在 redux-thunk 中缓存异步 api 调用?
【发布时间】:2020-10-06 13:21:02
【问题描述】:

我是 redux 的新手。 我现在正在开发一个显示每个国家/地区的足球联赛列表的应用程序。 首先,我正在获取一个国家/地区列表。之后,我使用国家名称循环遍历所有国家以获取足球联赛。不是每个国家都有足球联赛,所以我收到了一些 null 作为回复,我过滤掉了。然后我点击一个联赛,我被重定向到一个联赛页面。现在是棘手的部分。当我单击“返回”时,我转到我的主页,但整个 api 调用过程再次被触发。为什么?如何预防?如何只使用我获取的数据,并且只在需要时使用它。

如果我猜的话,错误出在减速器的某个地方。我尝试在那里将获取的 api 调用缓存在一个对象 (data: { ...state.data, ...}) 中,但不确定我是否正确执行此操作。 我可以做的第二个地方是useEffect。当然,其他任何事情也是可能的。

请帮忙!

这是我的代码:

App.js 我使用 react-router-dom 在容器之间移动:

import React from 'react';
import {Switch, Route, NavLink, Redirect} from "react-router-dom";
import SignedIn from '../signedIn/signedIn';
import SignedOut from '../signedOut/signedOut';

//Components/Containers
import AllLeagues from '../allLeagues/allLeagues/allLeagues';
import League from "../allLeagues/league/league";

const App = () => {
  return (
    <div className="App">
      <nav>
        <NavLink to={"/"}>SEARCH</NavLink>
      </nav>
      <Switch>
        <Route path={"/"} exact component={AllLeagues} />
        <Route path={"/allLeagues/:league"} exact component={League} />
        <Route path={"/signedin"} exact component={SignedIn} />
        <Route path={"/signedout"} exact component={SignedOut} />
        <Redirect to={"/"} />
      </Switch>
    </div>
  );
}

export default App;

这是我的页面,我在其中调用 api 来获取国家和足球联赛: allLeagues.js

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {Link} from "react-router-dom";

import _ from "lodash";
import shortid from "shortid";

import  { allLeagues }  from "../../../actions/leagues/allLeagues/allLeagues";
import  { allCountries }  from "../../../actions/allCountries/allCountries";

//the api provides 255 country names.
const ALL_COUNTRIES_LENGTH = 254;

const AllLeagues = () => {
    const dispatch = useDispatch();
    const selectAllCountries = useSelector(state => state.allCountries);
    const selectAllLeagues = useSelector(state => state.allLeagues);

    useEffect(() => {
        dispatch(allCountries());
    }, [dispatch]);

    useEffect(() => {
        if(!_.isEmpty(selectAllCountries.data)) {
            selectAllCountries.data.countries.map(el => dispatch(allLeagues(el.name_en)));
        }
    }, [dispatch, selectAllCountries.data]);

    let allCountriesArr = [];
    let allLeaguesFiltered = [];
    let getAllLeagues = [];

    allCountriesArr = (Object.values(selectAllLeagues.data));

    console.log(Object.values(selectAllLeagues.data));

    if(allCountriesArr.length > ALL_COUNTRIES_LENGTH) {
        allLeaguesFiltered = allCountriesArr.flat().filter(el => el !== null);
        getAllLeagues = allLeaguesFiltered.flat();
    }

    let getAllZeroDivisionLeagues = [];
    let getAllFirstDivisionLeagues = [];
    let getAllSecondDivisionLeagues = [];
    let getAllThirdDivisionLeagues = [];
    if(!_.isEmpty(getAllLeagues)) {
        getAllZeroDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "0");
        getAllFirstDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "1");
        getAllSecondDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "2");
        getAllThirdDivisionLeagues = getAllLeagues.filter(el => el.strDivision === "3");
    }

    const showData = () => {
        if(!_.isEmpty(selectAllLeagues.data)) {
            return(
                <div>
                Most Favorited Leagues:
                <br/>
                {getAllZeroDivisionLeagues.map(el => {
                    return (
                        <div key={shortid.generate()}>
                            <p>{el.strLeague}</p>
                            <Link to={`/allLeagues/${el.strLeague}`}>View</Link>
                        </div>
                    )}
                )}
                <br/>
                <br/>
                First Leagues:
                <br/>
                {getAllFirstDivisionLeagues.map(el => {
                    return (
                        <div key={shortid.generate()}>
                            <p>{el.strLeague}</p>
                            <Link to={`/allLeagues/${el.strLeague}`}>View</Link>
                        </div>
                    )}
                )}
                <br/>
                <br/>
                Second Leagues:
                <br/>
                {getAllSecondDivisionLeagues.map(el => {
                    return (
                        <div key={shortid.generate()}>
                            <p>{el.strLeague}</p>
                            <Link to={`/allLeagues/${el.strLeague}`}>View</Link>
                        </div>
                    )}
                )}
                <br/>
                <br/>
                Third Leagues:
                <br/>
                {getAllThirdDivisionLeagues.map(el => {
                    return (
                        <div key={shortid.generate()}>
                            <p>{el.strLeague}</p>
                            <Link to={`/allLeagues/${el.strLeague}`}>View</Link>
                        </div>
                    )}
                )}
                </div>
            )
        }
        
        if (selectAllLeagues.loading) {
            return <p>loading...</p>
        }

        if (selectAllLeagues.errorMsg !== "") {
            return <p>{selectAllLeagues.errorMsg}</p>
        }

        return <p>Loading...</p>;
    }

return (
    <div>
        <br/>
        <br/>
        All Leagues:
        <br />
        <br />
        {showData()}
    </div>
)
}

export default AllLeagues;

两个动作文件: allCountries.js

import { GET_ALL_COUNTRIES_LOADING, GET_ALL_COUNTRIES_SUCCESS, GET_ALL_COUNTRIES_FAIL } from "../index";
import theSportsDB from "../../apis/theSportsDB";

export const allCountries = () => async (dispatch) => { 
    try {
        dispatch ({
            type: GET_ALL_COUNTRIES_LOADING
        })

        const response = await theSportsDB.get("all_countries.php");
        
        dispatch ({
            type: GET_ALL_COUNTRIES_SUCCESS,
            payload: response.data
        })
    } catch (e) {
        dispatch ({
            type: GET_ALL_COUNTRIES_FAIL
        })
    }    
}

和 allCountriesReducer:

import {GET_ALL_COUNTRIES_LOADING, GET_ALL_COUNTRIES_SUCCESS, GET_ALL_COUNTRIES_FAIL} from "../../actions/index";

const DefaultState = {
    loading: false,
    data: [],
    errorMsg: ""
};

const AllCountriesReducer = (state = DefaultState, action) => {
    switch (action.type){
        case GET_ALL_COUNTRIES_LOADING:
            return {
                ...state,
                loading: true,
                errorMsg: ""
            };
            case GET_ALL_COUNTRIES_SUCCESS:
            return {
                ...state,
                loading: false,
                data: {
                    ...state.data,
                    countries: action.payload.countries
                },
                errorMsg: ""
            };
            case GET_ALL_COUNTRIES_FAIL:
            return {
                ...state,
                loading: false,
                errorMsg: "unable to get all the Countries"
            };
        default:
            return state;
    }
}

export default AllCountriesReducer;

现在是我用来获取所有联赛(带有国家名称,我从 allCountries 获得)的文件:

import { GET_ALL_LEAGUES_LOADING, GET_ALL_LEAGUES_SUCCESS, GET_ALL_LEAGUES_FAIL } from "../../index";
import theSportsDB from "../../../apis/theSportsDB";

export const allLeagues = (country) => async (dispatch) => { 
    try {
        dispatch ({
            type: GET_ALL_LEAGUES_LOADING
        })

        const response = await theSportsDB.get(`search_all_leagues.php?c=${country}&s=Soccer`);
        
        dispatch ({
            type: GET_ALL_LEAGUES_SUCCESS,
            payload: response.data,
            countryName: country
        })
    } catch (e) {
        dispatch ({
            type: GET_ALL_LEAGUES_FAIL
        })
    }    
}

还有减速机, allLeaguesReducer.js

import {GET_ALL_LEAGUES_LOADING, GET_ALL_LEAGUES_SUCCESS, GET_ALL_LEAGUES_FAIL} from "../../../actions/index";

const DefaultState = {
    loading: false,
    data: {},
    errorMsg: ""
};

const AllLeaguesReducer = (state = DefaultState, action) => {
    switch (action.type){
        case GET_ALL_LEAGUES_LOADING:
            return {
                ...state,
                loading: true,
                errorMsg: ""
            };
            case GET_ALL_LEAGUES_SUCCESS:
            return {
                ...state,
                loading: false,
                data:{
                    ...state.data,
                    [action.countryName]: action.payload.countrys
                },
                errorMsg: ""
            };
            case GET_ALL_LEAGUES_FAIL:
            return {
                ...state,
                loading: false,
                errorMsg: "unable to get all the leagues"
            };
        default:
            return state;
    }
}

export default AllLeaguesReducer;

还有联赛页面本身:

import React, { useEffect } from "react";
import { useSelector, useDispatch } from "react-redux";
import {Link} from "react-router-dom";

import _ from "lodash";
import shortid from "shortid";

import { getLeague } from "../../../actions/leagues/league/getLeague";

const League = (props) => {
    const leagueName = props.match.params.league;
    const dispatch = useDispatch();
    const selectLeague = useSelector(state => state.league);

    useEffect (() => {
        dispatch(getLeague(leagueName));
    }, [dispatch, leagueName]);

     const showLeague = () => {
         if(!_.isEmpty(selectLeague.data)) {
            return selectLeague.data.teams.map(el => {
                return (
                    <div key={shortid.generate()}>
                        {el.strTeam}
                    </div>
                )
            })
         }

         if(selectLeague.loading) {
            return <p>loading...</p>
         }

         if(selectLeague.errorMsg !== "") {
         return <p>{selectLeague.errorMsg}</p>
         }

         return <p>Unable to get the league data</p>
     }

    return (
        <div>
            <p>{leagueName}</p>
            {showLeague()}
            <Link to={"/"}>Back</Link>
        </div>
    )
}

export default League;

它的动作文件:

import { GET_LEAGUE_LOADING, GET_LEAGUE_SUCCESS, GET_LEAGUE_FAIL } from "../../index";
import theSportsDB from "../../../apis/theSportsDB";

export const getLeague = (league) => async (dispatch) => {
    try {
        dispatch ({
            type: GET_LEAGUE_LOADING
        })

        const response = await theSportsDB.get(`search_all_teams.php?l=${league}`);

        dispatch ({
            type: GET_LEAGUE_SUCCESS,
            payload: response.data,
            // leagueName: league
        })
    } catch (e) {
        dispatch ({
            type: GET_LEAGUE_FAIL
        })
    }
}

和减速器:

import { GET_LEAGUE_LOADING, GET_LEAGUE_SUCCESS, GET_LEAGUE_FAIL } from "../../../actions/index";

const DefaultState = {
    loading: false,
    data: {},
    errorMsg: ""
};

const LeagueReducer = (state = DefaultState, action) => {
    switch (action.type) {
        case GET_LEAGUE_LOADING:
            return {
                ...state,
                loading: true,
                errorMsg: ""
            };

        case GET_LEAGUE_SUCCESS:
            return {
                ...state,
                loading: false,
                data: action.payload,
                errorMsg: ""
            };

        case GET_LEAGUE_FAIL:
            return {
                ...state,
                loading: false,
                errorMsg: "league not found"
            };
    default:
        return state
    }
}

export default LeagueReducer;

在 Redux 开发工具中,当我按下返回以再次回到我的主页时,会触发以下内容(在状态栏中): GET_ALL_COUNTRIES_LOADING 一段时间后: GET_ALL_LEAGUES_SUCCESS 再次。所以它正在再次进行api调用。

【问题讨论】:

  • 您可能需要在useEffect 中使用条件——仅当selectAllCountries.data 为空时才调度。
  • 如果我将 selectAllCountrires.data 从 useEffect() 中取出,则不会呈现任何内容:/.
  • 如果我将 selectAllCountrires.data 从 useEffect() 中取出,则不会呈现任何内容:/。我尝试制作一个自定义钩子并将 useEffect() 放在那里:const useCountries = getCountries =&gt; { useEffect(() =&gt; { dispatch(getCountries()); }, [getCountries]) } useCountries(allCountries); 如此处建议:stackoverflow.com/questions/54930197/… 但它没有帮助。 useEffect() 在每个组件渲染上渲染。
  • 在调度内部执行此操作 -- if (selectAllCountries.data.length &lt; 1) {...}。否则每次加载页面时都会触发 useEffect。
  • 感谢您的所有帮助。这当然是我一直在寻找的解决方案之一。与此同时,我发现了getstore()(在“./actions/...”中)并使用这种方法来存储数据。您可以使用if (selectAllCountries.data.length &lt; 1) 发布答案以便我接受吗?

标签: javascript reactjs redux redux-thunk


【解决方案1】:

您需要在useEffect 中使用条件,这样它就不会在您每次加载页面时运行。

试试这个:

useEffect(() => {
    if (selectAllCountries.data.length < 1) {
        disptch(getCountries());
    }
})

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2016-12-11
    • 2019-06-07
    • 2020-12-01
    • 2020-10-28
    • 2017-08-28
    • 2017-10-23
    • 2021-12-11
    • 2019-11-12
    相关资源
    最近更新 更多