在 React-redux 和 Redux-Thunk 中从 API 获取数据的问题

Posted

技术标签:

【中文标题】在 React-redux 和 Redux-Thunk 中从 API 获取数据的问题【英文标题】:Problems with getting data from API in React-redux and Redux-Thunk 【发布时间】:2019-03-15 21:58:23 【问题描述】:

我很难理解 react-redux 和 thunks 库的工作原理。

我想要实现的是在访问页面时使用一些 API 来获取所有帖子。

我正在调用componentDidMount() 函数中的API。 从我注意到的情况来看,我的代码被执行了 3 次,其中最后一个得到了帖子。

这是我的postReducer.js

import * as types from "../actions/actionTypes";
import initialState from "../reducers/initialState";

export function postsHaveError(state = false, action) 
  switch (action.type) 
    case types.LOAD_POSTS_ERROR:
      return action.hasError;

    default:
      return state;
  


export function postsAreLoading(state = false, action) 
  switch (action.type) 
    case types.LOADING_POSTS:
      return action.isLoading;

    default:
      return state;
  


export function posts(state = initialState.posts, action) 
  switch (action.type) 
    case types.LOAD_POSTS_SUCCESS:
      return action.posts;

    default:
      return state;
  

// export default rootReducer;

postAction.js

import * as types from "./actionTypes";
import axios from "axios";

    export function postsHaveError(bool) 
      return 
        type: types.LOAD_POSTS_ERROR,
        hasError: bool
      ;
    

    export function postsAreLoading(bool) 
      return 
        type: types.LOADING_POSTS,
        isLoading: bool
      ;
    

    export function postsFetchDataSuccess(posts) 
      return 
        type: types.LOAD_POSTS_SUCCESS,
        posts
      ;
    

    export function postsFetchData(url) 
      return dispatch => 
        dispatch(postsAreLoading(true));

        axios
          .get(url)
          .then(response => 
            if (response.status !== 200) 
              throw Error(response.statusText);
            

            dispatch(postsAreLoading(false));

            return response;
          )
          .then(response => dispatch(postsFetchDataSuccess(response.data)))
          .catch(() => dispatch(postsHaveError(true)));
      ;
    

以及我试图获取帖子的组件。

import React from "react";
import PostItem from "./PostItem";
import  connect  from "react-redux";
import  postsFetchData  from "../../actions/postActions";

class BlogPage extends React.Component 
  constructor(props) 
    super(props);

    this.state = 
      data: null
    ;
  

  componentDidMount() 
   this.props.fetchData("http://localhost:3010/api/posts");
  

  render() 
    if (this.props.hasError) 
      return <p>Sorry! There was an error loading the items</p>;
    

    if (this.props.isLoading) 
      return <p>Loading…</p>;
    

    console.log(this.props);
    return (
      <div>
        <div className="grid-style">
          <PostItem <<once i have posts they should go here>> />
        </div>
      </div>
    );
  


const mapStateToProps = state => 
  return 
    posts: state.posts,
    hasError: state.postsHaveError,
    isLoading: state.postsAreLoading
  ;
;

const mapDispatchToProps = dispatch => 
  return 
    fetchData: url => dispatch(postsFetchData(url))
  ;
;

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(BlogPage);

index.js

import React from "react";
import ReactDOM from "react-dom";
import "./index.css";
import App from "./App";
import registerServiceWorker from "./registerServiceWorker";
import  BrowserRouter  from "react-router-dom";
import configureStore from "./store/configureStore";
import  Provider  from "react-redux";

const store = configureStore();

ReactDOM.render(
  <Provider store=store>
    <BrowserRouter>
      <App />
    </BrowserRouter>
  </Provider>,
  document.getElementById("root")
);

registerServiceWorker();

app.js

import React,  Component  from "react";
import "./App.css";
import Header from "./components/common/header.js";
import Footer from "./components/common/footer.js";
import Main from "./components/common/main.js";
import "./layout.scss";

class App extends Component 
  render() 
    return (
      <div className="App">
        <Header />
        <Main />
        <Footer />
      </div>
    );
  


export default App;

main.js BlogPage 所在的位置。

import React from 'react';
import BlogPage from '../blog/BlogPage';
import AboutPage from '../about/AboutPage';
import  Route, Switch  from 'react-router-dom';
import LoginPage from '../authentication/LoginPage';

const Main = () => 
  return (
    <div>
      <section id="one" className="wrapper style2">
        <div className="inner">
          <Switch>
            <Route path="/about" component=AboutPage />
            <Route path="/login" component=LoginPage />
            <Route path="/" component=BlogPage />
          </Switch>
        </div>
      </section>
    </div>
  );
;

export default Main;

【问题讨论】:

你的reducer中缺少一个功能吗?上传成功了吗? 为什么你有3个reducer?您应该将其合并到一个减速器中。另外,你有结果吗?如果没有,可能你的 redux-thunk 没有正确配置。 【参考方案1】:

您的问题与question 非常相似(我还包括一个代码框供您使用)。请通读它并按照工作示例进行操作,最重要的是,阅读 7 个提示(有些可能不适用于您的项目;但是,我强烈建议您安装 prop-types 以在您偏离 1:1 时发出警告还原状态)。

您面临的问题与此函数 postsFetchData 未返回 axios 承诺有关(您还有一个不必要的 .then() 已被删除 - 此示例与下面提供的示例一起使用):

actions/blogActions.js

import * as types from '../types';

export const postsFetchData = () => dispatch => 
  // dispatch(postsAreLoading(true)); // <== not needed

  return axios
    .get("http://localhost:3010/api/posts") // API url should be declared here
    .then(( data ) =>  // es6 destructuring, data = response.data
       /* if (response.status !== 200) 
          throw Error(response.statusText);
         */ // <== not needed, the .catch should catch this

       // dispatch(postsAreLoading(false)); // <== not needed
       // dispatch(postsFetchDataSuccess(response.data)) // <== not needed, just return type and payload

       dispatch( type: types.LOAD_POSTS_SUCCESS, payload: data )
     )
    .catch(err => dispatch( type: types.LOAD_POSTS_ERROR, payload: err.toString() ));

如链接问题中所述,您不需要带有 Redux 连接容器组件的 isLoading。由于 props 来自 redux 的 store,React 会看到 props 的变化并相应地更新连接的组件。相反,您可以使用本地 React 状态,或者只是检查数据是否存在。

下面的示例检查数据是否存在,否则正在加载...

BlogPage.js

import isEmpty from "lodash/isEmpty";
import React,  PureComponent  from "react";
import  connect  from "react-redux";
import PostItem from "./PostItem";
import  postsFetchData  from "../../actions/blogActions";

class BlogPage extends PureComponent 

  componentDidMount => () => this.props.postsFetchData(); // the API url should be placed in action creator, not here, especially if it's static 

  render = () => (
    this.props.hasError // if this was an error...
      ? <p>Sorry! There was an error loading the items: this.props.hasError</p> // then an show error
      : isEmpty(this.props.posts) // otherwise, use lodash's isEmpty to determine if the posts array exists AND has a length of 0, if it does...
         ? <p>Loading…</p> // then show loading...
         : <div className="grid-style"> // otherwise, if there's no error, and there are posts in the posts array...
             <PostItem posts=this.props.posts /> // then show PostItem
           </div>
   )


export default connect(state => ( 
  // this is just inline mapStateToProps
  posts: state.blog.posts 
  hasError: state.blog.hasError
),
   postsFetchData  // this is just an inline mapDispatchToProps
)(BlogPage);

reducers/index.js

import  combineReducers  from 'redux';
import * as types from '../types';

const initialState = 
  posts: [], // posts is declared as an array and should stay that way
  hasError: '' // hasError is declared as string and should stay that way


const blogPostsReducer = (state = initialState,  type, payload ) => 
  switch (type) 
    case types.LOAD_POSTS_SUCCESS:
      return  ...state, posts: payload, hasError: '' ; // spread out any state, then update posts with response data and clear hasError
    case types.LOAD_POSTS_ERROR:
      return  ...state, hasError: payload ; // spread out any state, and update hasError with the response error
    default:
      return state;
  


export default combineReducers(
  blog: blogPostReducer
  // include any other reducers here
)

BlogPage.js(带有 isLoading 本地 React 状态)

import isEqual from "lodash/isEqual";
import isEmpty from "lodash/isEmpty";
import React,  Component  from "react";
import  connect  from "react-redux";
import PostItem from "./PostItem";
import  postsFetchData  from "../../actions/blogActions";

class BlogPage extends Component 
  state =  isLoading: true ;

  componentDidUpdate = (prevProps) =>  // triggers when props have been updated
    const  posts  =  this.props; // current posts
    const prevPosts = prevProps.posts; // previous posts
    const  hasError  = this.props; // current error
    const prevError = prevProps.hasError // previous error

    if (!isEqual(posts,prevPosts) || hasError !== prevError)  // if the current posts array is not equal to the previous posts array or current error is not equal to previous error...
      this.setState( isLoading: false ); // turn off loading
    
  

  componentDidMount => () => this.props.postsFetchData(); // fetch data

  render = () => (
    this.state.isLoading // if isLoading is true...
      ? <p>Loading…</p> // then show loading...          
      : this.props.hasError // otherwise, if there was an error...
          ? <p>Sorry! There was an error loading the items: this.props.hasError</p> // then an show error
          : <div className="grid-style"> // otherwise, if isLoading is false and there's no error, then show PostItem
              <PostItem posts=this.props.posts />
            </div>
   )


export default connect(state => ( 
  // this is just inline mapStateToProps
  posts: state.blog.posts 
  hasError: state.blog.hasError
),
   postsFetchData  // this is just an inline mapDispatchToProps
)(BlogPage);

【讨论】:

这是一个很好的总结。 如链接问题中所述,您不需要 isLoading 与 Redux 连接的容器组件 - 否则无法连接此示例的加载指示器,不是吗?在 IRL 中,这可能会通过 Axios 拦截器完成。 不可能,不。只需使用本地反应状态。我将更新我的答案以包含另一个使用 state 的版本。 更新答案以包含isLoading 示例。 不错的一个。我认为使用 Redux 管理它没有问题,但本地状态不会对这项任务造成伤害。 如前所述,BlogPage 组件可以完全由 React 本地状态管理。不需要 Redux。我很想只使用当地状态来重写我的答案......

以上是关于在 React-redux 和 Redux-Thunk 中从 API 获取数据的问题的主要内容,如果未能解决你的问题,请参考以下文章

react-redux 中的 ref 和 node 指的是啥?

无论在寄存器中输入啥,使用 react-redux 和 jwt 都会出现“请输入所有字段错误”

React-Redux的使用

使用 connect 与 react-redux 和 redux-persist

使用 React-Redux Hooks 和 React-Redux Connect() 的主要区别是啥?

在 React-redux 和 Redux-Thunk 中从 API 获取数据的问题