在 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 都会出现“请输入所有字段错误”
使用 connect 与 react-redux 和 redux-persist