使用 React、Redux 和 Axios 处理异步请求?
Handling async request with React, Redux and Axios?
我是 React JS 和 Redux 的新手,它太难了,无法开始。我正在尝试使用 Axios 发出 POST 请求,但我无法做到。可能是我在容器文件中遗漏了一些东西。下面是代码。检查 plnkr
更新: 我在提交后收到@@redux-form/SET_SUBMIT_SUCCEEDED 消息。但是当我检查网络选项卡时,我没有看到对 API 的调用。而且当我安慰提交的值时,我只看到名称和全名值。它不包含徽标和详细信息。我错过了什么?
组件文件
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Field,reduxForm } from 'redux-form'
import { Columns,Column, TextArea, Label,Button } from 'bloomer'
import FormField from 'FormField'
const validate = (values) => {
const errors = {}
const requiredFields =
['organizationName','organizationFullName','organizationDetails']
requiredFields.forEach((field) => {
if (!values[field]) {
errors[field] = 'This field can\'t be empty!'
}
})
return errors
}
const formConfig = {
validate,
form: 'createOrganization',
enableReinitialize: true
}
export class CreateOrganization extends PureComponent {
static propTypes = {
isLoading:PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired, // from react-redux
submitting: PropTypes.bool.isRequired // from react-redux
}
onSubmit = data => {
console.log(data)
}
render () {
const { handleSubmit,submitting,isLoading } = this.props
return (
<Columns isCentered>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))} >
<Column isSize='3/6' >
<Label>Organization Name</Label>
<Field
name="organizationName"
component={FormField}
type="text"
placeholder="Organization Name"
/>
</Column>
<Column isSize='3/6'>
<Label>Organization Full Name</Label>
<Field
name="organizationFullName"
component={FormField}
type="text"
placeholder="Organization Full Name"
/>
</Column>
<Column isSize='3/6'>
<Label>Organization Logo</Label>
<Input
name="organizationLogo"
type="file"
placeholder="Logo"
/>
</Column>
<Column isSize='3/6'>
<Label>Organization Details</Label>
<TextArea placeholder={'Enter Details'} />
</Column>
<Column >
<span className="create-button">
<Button type="submit" isLoading={submitting || isLoading} isColor='primary'>
Submit
</Button>
</span>
<Button type="button" isColor='danger'>
Cancel
</Button>
</Column>
</form>
</Columns>
)
}
}
export default reduxForm(formConfig)(CreateOrganization)
容器文件
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Loader from 'Loader'
import organization from 'state/organization'
import CreateOrganization from '../components/createOrganization'
export class Create extends PureComponent {
static propTypes = {
error: PropTypes.object,
isLoaded: PropTypes.bool.isRequired,
create: PropTypes.func.isRequired,
}
onSubmit = data => {
this.props.create(data)
}
render () {
const { isLoaded, error } = this.props
return (
<CreateOrganization onSubmitForm={this.onSubmit} isLoading=
{isLoading} />
)
}
}
const mapStateToProps = state => ({
error: organization.selectors.getError(state),
isLoading: organization.selectors.isLoading(state)
})
const mapDispatchToProps = {
create: organization.actions.create
}
export default connect(mapStateToProps, mapDispatchToProps)(Create)
Redux 操作创建者显然不支持异步操作,而这正是您尝试对 post 请求执行的操作。 Redux Thunk 应该可以帮助解决这个问题。
您需要一个如下所示的 store.js 文件:
//npm install --save redux-thunk
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducer.js';
// Note: this API requires redux@>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk) //needed so you can do async
);
这是您的操作文件的样子。 Create 成为一个动作创建者,returns 一个函数,然后执行 post 请求并允许您在那里进行调度,从而允许您更新 store/state。 :
import axios from 'axios'
import { CREATE_ORGANIZATION, CREATE_ORGANIZATION_SUCCESS, CREATE_ORGANIZATION_FAILURE,
} from './constants'
import * as selectors from './selectors'
/*
CREATE ORGANIZATION
*/
//uses redux-thunk to make the post call happen
export function create (values) {
return function(dispatch) {
return axios.post('/url', values).then((response) => {
dispatch({ type: 'Insert-constant-here'})
console.log(response);
})
}
}
此外,您需要像这样将创建的 onSubmit 方法传递给 onSubmitForm。我不确定 isLoading 是从哪里来的,因为我没有看到它被导入到那个容器组件中,所以你可能也想看看它。:
<createOrganization onSubmitForm={this.onSubmit.bind(this)} isLoading={isLoading} />
你可以在 redux-saga 的帮助下轻松做到这一点。
关于 redux-saga:
redux-saga
是一个库,旨在使应用程序的副作用(即数据获取等异步事物和访问浏览器缓存等不纯事物)更易于管理、执行更高效、测试更简单,并且更善于处理失败。
安装:
$ npm install --save redux-saga
或
$ yarn add redux-saga
您的 redux 操作创建者必须是普通的、对象的,并且应该使用强制键 type
进行分派和操作。但是,使用像 redux-thunk
这样的自定义中间件,你可以在你的动作创建者中调用 axios
请求,因为没有自定义 middlewares
你的动作创建者需要 return 普通对象
您的动作创建器看起来像
export function create (values) {
return (dispatch) => {
dispatch({type: CREATE_ORGANIZATION});
axios.post('/url', values)
.then((res) =>{
dispatch({type: CREATE_ORGANIZATION_SUCCESS, payload: res});
})
.catch((error)=> {
dispatch({type: CREATE_ORGANIZATION_FAILURE, payload: error});
})
}
}
你的减速器看起来像
export default (state = initialState, action) => {
const payload = action.payload
switch (action.type) {
case CREATE:
return {
...state,
loading: true,
loaded: false
}
case CREATE_SUCCESS:
return {
...state,
data: state.data.concat(payload.data),
loading: false,
loaded: true,
error: null
}
}
case CREATE_FAILURE:
return {
...state,
loading: false,
loaded: true,
error: payload
}
default:
return state
}
}
现在您可以在创建商店时像
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
const store = createStore(
reducer,
applyMiddleware(thunk)
);
除此之外你还需要设置 redux 表单
你需要使用 combineReducers 和 Provider 来传递 store
import reducer from './reducer';
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form'
export const rootReducer = combineReducers({
reducer,
form: formReducer
})
我建议使用 redux-promise-middleware。这个库要求动作有一个名为 payload
的 属性,它是一个 promise,这很容易用 axios
。然后它与 Redux
集成,为根操作类型(例如 GET_CUSTOMERS
)添加 PENDING
、FULFILLED
和 REJECTED
后缀,并触发这些操作。
触发动作与任何其他动作相同。
商店
import {applyMiddleware, compose, createStore} from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import reducer from './reducers';
let middleware = applyMiddleware(promiseMiddleware());
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const enhancer = composeEnhancers(middleware);
export default createStore(reducer, enhancer);
动作
export function getCustomers() {
return {
type: 'GET_CUSTOMERS',
payload: axios.get('url/to/api')
.then(res => {
if (!res.ok) throw new Error('An error occurred.');
return res;
})
.then(res => res.json())
.catch(err => console.warn(err));
};
}
减速机
export default function(state = initialState, action) => {
switch (action.type) {
case 'GET_CUSTOMERS_PENDING':
// this means the call is pending in the browser and has not
// yet returned a response
...
case 'GET_CUSTOMERS_FULFILLED':
// this means the call is successful and the response has been set
// to action.payload
...
case 'GET_CUSTOMERS_REJECTED':
// this means the response was unsuccessful so you can handle that
// error here
...
default:
return state;
}
}
我是 React JS 和 Redux 的新手,它太难了,无法开始。我正在尝试使用 Axios 发出 POST 请求,但我无法做到。可能是我在容器文件中遗漏了一些东西。下面是代码。检查 plnkr
更新: 我在提交后收到@@redux-form/SET_SUBMIT_SUCCEEDED 消息。但是当我检查网络选项卡时,我没有看到对 API 的调用。而且当我安慰提交的值时,我只看到名称和全名值。它不包含徽标和详细信息。我错过了什么?
组件文件
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { Field,reduxForm } from 'redux-form'
import { Columns,Column, TextArea, Label,Button } from 'bloomer'
import FormField from 'FormField'
const validate = (values) => {
const errors = {}
const requiredFields =
['organizationName','organizationFullName','organizationDetails']
requiredFields.forEach((field) => {
if (!values[field]) {
errors[field] = 'This field can\'t be empty!'
}
})
return errors
}
const formConfig = {
validate,
form: 'createOrganization',
enableReinitialize: true
}
export class CreateOrganization extends PureComponent {
static propTypes = {
isLoading:PropTypes.bool.isRequired,
handleSubmit: PropTypes.func.isRequired, // from react-redux
submitting: PropTypes.bool.isRequired // from react-redux
}
onSubmit = data => {
console.log(data)
}
render () {
const { handleSubmit,submitting,isLoading } = this.props
return (
<Columns isCentered>
<form onSubmit={handleSubmit(this.onSubmit.bind(this))} >
<Column isSize='3/6' >
<Label>Organization Name</Label>
<Field
name="organizationName"
component={FormField}
type="text"
placeholder="Organization Name"
/>
</Column>
<Column isSize='3/6'>
<Label>Organization Full Name</Label>
<Field
name="organizationFullName"
component={FormField}
type="text"
placeholder="Organization Full Name"
/>
</Column>
<Column isSize='3/6'>
<Label>Organization Logo</Label>
<Input
name="organizationLogo"
type="file"
placeholder="Logo"
/>
</Column>
<Column isSize='3/6'>
<Label>Organization Details</Label>
<TextArea placeholder={'Enter Details'} />
</Column>
<Column >
<span className="create-button">
<Button type="submit" isLoading={submitting || isLoading} isColor='primary'>
Submit
</Button>
</span>
<Button type="button" isColor='danger'>
Cancel
</Button>
</Column>
</form>
</Columns>
)
}
}
export default reduxForm(formConfig)(CreateOrganization)
容器文件
import React, { PureComponent } from 'react'
import PropTypes from 'prop-types'
import { connect } from 'react-redux'
import Loader from 'Loader'
import organization from 'state/organization'
import CreateOrganization from '../components/createOrganization'
export class Create extends PureComponent {
static propTypes = {
error: PropTypes.object,
isLoaded: PropTypes.bool.isRequired,
create: PropTypes.func.isRequired,
}
onSubmit = data => {
this.props.create(data)
}
render () {
const { isLoaded, error } = this.props
return (
<CreateOrganization onSubmitForm={this.onSubmit} isLoading=
{isLoading} />
)
}
}
const mapStateToProps = state => ({
error: organization.selectors.getError(state),
isLoading: organization.selectors.isLoading(state)
})
const mapDispatchToProps = {
create: organization.actions.create
}
export default connect(mapStateToProps, mapDispatchToProps)(Create)
Redux 操作创建者显然不支持异步操作,而这正是您尝试对 post 请求执行的操作。 Redux Thunk 应该可以帮助解决这个问题。
您需要一个如下所示的 store.js 文件:
//npm install --save redux-thunk
import { createStore, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import rootReducer from './reducer.js';
// Note: this API requires redux@>=3.1.0
const store = createStore(
rootReducer,
applyMiddleware(thunk) //needed so you can do async
);
这是您的操作文件的样子。 Create 成为一个动作创建者,returns 一个函数,然后执行 post 请求并允许您在那里进行调度,从而允许您更新 store/state。 :
import axios from 'axios'
import { CREATE_ORGANIZATION, CREATE_ORGANIZATION_SUCCESS, CREATE_ORGANIZATION_FAILURE,
} from './constants'
import * as selectors from './selectors'
/*
CREATE ORGANIZATION
*/
//uses redux-thunk to make the post call happen
export function create (values) {
return function(dispatch) {
return axios.post('/url', values).then((response) => {
dispatch({ type: 'Insert-constant-here'})
console.log(response);
})
}
}
此外,您需要像这样将创建的 onSubmit 方法传递给 onSubmitForm。我不确定 isLoading 是从哪里来的,因为我没有看到它被导入到那个容器组件中,所以你可能也想看看它。:
<createOrganization onSubmitForm={this.onSubmit.bind(this)} isLoading={isLoading} />
你可以在 redux-saga 的帮助下轻松做到这一点。
关于 redux-saga:
redux-saga
是一个库,旨在使应用程序的副作用(即数据获取等异步事物和访问浏览器缓存等不纯事物)更易于管理、执行更高效、测试更简单,并且更善于处理失败。
安装:
$ npm install --save redux-saga
或
$ yarn add redux-saga
您的 redux 操作创建者必须是普通的、对象的,并且应该使用强制键 type
进行分派和操作。但是,使用像 redux-thunk
这样的自定义中间件,你可以在你的动作创建者中调用 axios
请求,因为没有自定义 middlewares
你的动作创建者需要 return 普通对象
您的动作创建器看起来像
export function create (values) {
return (dispatch) => {
dispatch({type: CREATE_ORGANIZATION});
axios.post('/url', values)
.then((res) =>{
dispatch({type: CREATE_ORGANIZATION_SUCCESS, payload: res});
})
.catch((error)=> {
dispatch({type: CREATE_ORGANIZATION_FAILURE, payload: error});
})
}
}
你的减速器看起来像
export default (state = initialState, action) => {
const payload = action.payload
switch (action.type) {
case CREATE:
return {
...state,
loading: true,
loaded: false
}
case CREATE_SUCCESS:
return {
...state,
data: state.data.concat(payload.data),
loading: false,
loaded: true,
error: null
}
}
case CREATE_FAILURE:
return {
...state,
loading: false,
loaded: true,
error: payload
}
default:
return state
}
}
现在您可以在创建商店时像
import thunk from 'redux-thunk';
import { createStore, applyMiddleware } from 'redux';
const store = createStore(
reducer,
applyMiddleware(thunk)
);
除此之外你还需要设置 redux 表单
你需要使用 combineReducers 和 Provider 来传递 store
import reducer from './reducer';
import { combineReducers } from 'redux';
import { reducer as formReducer } from 'redux-form'
export const rootReducer = combineReducers({
reducer,
form: formReducer
})
我建议使用 redux-promise-middleware。这个库要求动作有一个名为 payload
的 属性,它是一个 promise,这很容易用 axios
。然后它与 Redux
集成,为根操作类型(例如 GET_CUSTOMERS
)添加 PENDING
、FULFILLED
和 REJECTED
后缀,并触发这些操作。
触发动作与任何其他动作相同。
商店
import {applyMiddleware, compose, createStore} from 'redux';
import promiseMiddleware from 'redux-promise-middleware';
import reducer from './reducers';
let middleware = applyMiddleware(promiseMiddleware());
const composeEnhancers = window.__REDUX_DEVTOOLS_EXTENSION_COMPOSE__ || compose;
const enhancer = composeEnhancers(middleware);
export default createStore(reducer, enhancer);
动作
export function getCustomers() {
return {
type: 'GET_CUSTOMERS',
payload: axios.get('url/to/api')
.then(res => {
if (!res.ok) throw new Error('An error occurred.');
return res;
})
.then(res => res.json())
.catch(err => console.warn(err));
};
}
减速机
export default function(state = initialState, action) => {
switch (action.type) {
case 'GET_CUSTOMERS_PENDING':
// this means the call is pending in the browser and has not
// yet returned a response
...
case 'GET_CUSTOMERS_FULFILLED':
// this means the call is successful and the response has been set
// to action.payload
...
case 'GET_CUSTOMERS_REJECTED':
// this means the response was unsuccessful so you can handle that
// error here
...
default:
return state;
}
}