根据文档,“没有中间件,Redux商店只支持同步数据流”。我不明白为什么会这样。为什么容器组件不能调用异步API,然后分派操作?
例如,想象一个简单的UI:一个字段和一个按钮。当用户按下按钮时,该字段将填充来自远程服务器的数据。
import * as React from 'react';
import * as Redux from 'redux';
import { Provider, connect } from 'react-redux';
const ActionTypes = {
STARTED_UPDATING: 'STARTED_UPDATING',
UPDATED: 'UPDATED'
};
class AsyncApi {
static getFieldValue() {
const promise = new Promise((resolve) => {
setTimeout(() => {
resolve(Math.floor(Math.random() * 100));
}, 1000);
});
return promise;
}
}
class App extends React.Component {
render() {
return (
<div>
<input value={this.props.field}/>
<button disabled={this.props.isWaiting} onClick={this.props.update}>Fetch</button>
{this.props.isWaiting && <div>Waiting...</div>}
</div>
);
}
}
App.propTypes = {
dispatch: React.PropTypes.func,
field: React.PropTypes.any,
isWaiting: React.PropTypes.bool
};
const reducer = (state = { field: 'No data', isWaiting: false }, action) => {
switch (action.type) {
case ActionTypes.STARTED_UPDATING:
return { ...state, isWaiting: true };
case ActionTypes.UPDATED:
return { ...state, isWaiting: false, field: action.payload };
default:
return state;
}
};
const store = Redux.createStore(reducer);
const ConnectedApp = connect(
(state) => {
return { ...state };
},
(dispatch) => {
return {
update: () => {
dispatch({
type: ActionTypes.STARTED_UPDATING
});
AsyncApi.getFieldValue()
.then(result => dispatch({
type: ActionTypes.UPDATED,
payload: result
}));
}
};
})(App);
export default class extends React.Component {
render() {
return <Provider store={store}><ConnectedApp/></Provider>;
}
}
渲染导出的组件时,我可以单击该按钮,输入将正确更新。
注意connect调用中的update函数。它发送一个动作,告诉应用程序它正在更新,然后执行一个异步调用。调用完成后,所提供的值将作为另一个操作的有效负载进行调度。
这种方法有什么问题?为什么我要使用Redux Thunk或Redux Promise,如文档所示?
编辑:我在Redux repo中搜索线索,发现Action Creator在过去被要求是纯函数。例如,这里有一个用户试图为异步数据流提供更好的解释:
动作创建者本身仍然是一个纯函数,但它返回的thunk函数不需要是纯函数,它可以执行我们的异步调用
动作创作者不再需要纯粹。因此,thunk/promise中间件在过去肯定是必需的,但现在似乎不再是这样了?
使用Redux saga是React Redux实现中最好的中间件。
前任:商店.js
import createSagaMiddleware from 'redux-saga';
import { createStore, applyMiddleware } from 'redux';
import allReducer from '../reducer/allReducer';
import rootSaga from '../saga';
const sagaMiddleware = createSagaMiddleware();
const store = createStore(
allReducer,
applyMiddleware(sagaMiddleware)
)
sagaMiddleware.run(rootSaga);
export default store;
然后saga.js
import {takeLatest,delay} from 'redux-saga';
import {call, put, take, select} from 'redux-saga/effects';
import { push } from 'react-router-redux';
import data from './data.json';
export function* updateLesson(){
try{
yield put({type:'INITIAL_DATA',payload:data}) // initial data from json
yield* takeLatest('UPDATE_DETAIL',updateDetail) // listen to your action.js
}
catch(e){
console.log("error",e)
}
}
export function* updateDetail(action) {
try{
//To write store update details
}
catch(e){
console.log("error",e)
}
}
export default function* rootSaga(){
yield [
updateLesson()
]
}
然后是action.js
export default function updateFruit(props,fruit) {
return (
{
type:"UPDATE_DETAIL",
payload:fruit,
props:props
}
)
}
然后reducer.js
import {combineReducers} from 'redux';
const fetchInitialData = (state=[],action) => {
switch(action.type){
case "INITIAL_DATA":
return ({type:action.type, payload:action.payload});
break;
}
return state;
}
const updateDetailsData = (state=[],action) => {
switch(action.type){
case "INITIAL_DATA":
return ({type:action.type, payload:action.payload});
break;
}
return state;
}
const allReducers =combineReducers({
data:fetchInitialData,
updateDetailsData
})
export default allReducers;
然后是main.js
import React from 'react';
import ReactDOM from 'react-dom';
import App from './app/components/App.jsx';
import {Provider} from 'react-redux';
import store from './app/store';
import createRoutes from './app/routes';
const initialState = {};
const store = configureStore(initialState, browserHistory);
ReactDOM.render(
<Provider store={store}>
<App /> /*is your Component*/
</Provider>,
document.getElementById('app'));
试试这个。。正在工作
要回答开头提出的问题:
为什么容器组件不能调用异步API,然后分派操作?
请记住,这些文档适用于Redux,而不是Redux+React。连接到React组件的Redux商店可以做到您所说的一切,但没有中间件的Plain Jane Redux商店不接受除普通对象之外的参数来分派。
如果没有中间件,你当然还可以
const store = createStore(reducer);
MyAPI.doThing().then(resp => store.dispatch(...));
但这是一个类似的情况,异步是围绕着Redux而不是由Redux处理的。因此,中间件通过修改可以直接传递给调度的内容来允许异步。
也就是说,我认为你的建议的精神是有效的。在Redux+Rreact应用程序中,当然还有其他方法可以处理异步。
使用中间件的一个好处是,您可以继续正常使用动作创建者,而不必担心它们是如何连接的。例如,使用redux thunk,您编写的代码看起来很像
function updateThing() {
return dispatch => {
dispatch({
type: ActionTypes.STARTED_UPDATING
});
AsyncApi.getFieldValue()
.then(result => dispatch({
type: ActionTypes.UPDATED,
payload: result
}));
}
}
const ConnectedApp = connect(
(state) => { ...state },
{ update: updateThing }
)(App);
它看起来与原始版本没有太大区别-只是有点混乱-connect不知道updateThing是(或需要)异步的。
如果您还想支持承诺、可观测性、传奇或疯狂的自定义和高度声明性的动作创建者,那么Redux可以通过更改传递给分派的内容(也就是从动作创建者返回的内容)来实现。无需干扰React组件(或连接调用)。