根据文件,"Without middleware, Redux store only supports synchronous data flow".我不明白为什么会这样.为什么容器组件不能调用异步API,然后调用操作?

例如,设想一个简单的UI:一个字段和一个按钮.当用户按下按钮时,该字段将填充来自远程服务器的数据.

A field and a button

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,正如文档所示?

EDIT:我在Redux repo中搜索线索,发现Action创建者在过go 被要求是纯函数.例如,here's a user trying to provide a better explanation for async data flow:

动作创建器本身仍然是一个纯函数,但是它返回的thunk函数不需要是,它可以执行我们的异步调用

Action creators are no longer required to be pure.那么,Thunk/Promise中间件在过go 是肯定需要的,但现在似乎不再是这样了?

推荐答案

这种方法有什么问题?为什么我要使用Redux Thunk或Redux Promise,正如文档所示?

这种方法没有错.这在大型应用程序中很不方便,因为不同的组件将执行相同的操作,您可能希望取消某些操作,或者将一些局部状态(如自动递增ID)保持在操作创建者附近,因此,从维护的Angular 来看,将动作创建者提取到单独的功能中更容易.

You can read 100 for a more detailed walkthrough.

像Redux Thunk或Redux Promise这样的中间件只会给你分派任务或promise 的"语法糖",但是你并不是百分之百地使用它.

因此,如果没有任何中间件,您的动作创建者可能看起来像

// action creator
function loadData(dispatch, userId) { // needs to dispatch, so it is first argument
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  loadData(this.props.dispatch, this.props.userId); // don't forget to pass dispatch
}

但是使用Thunk中间件,您可以这样编写:

// action creator
function loadData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`) // Redux Thunk handles these
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_DATA_FAILURE', err })
    );
}

// component
componentWillMount() {
  this.props.dispatch(loadData(this.props.userId)); // dispatch like you usually do
}

所以没有太大的区别.对于后一种方法,我喜欢的一点是,组件不关心动作创建者是否是异步的.它通常只调用dispatch,还可以使用mapDispatchToProps用简短的语法绑定这样的action creator,等等.组件不知道action creator是如何实现的,您可以在不同的异步方法(Redux Thunk、Redux Promise、Redux Saga)之间切换,而无需更改组件.另一方面,使用前一种显式方法,组件知道exactly特定调用是异步的,需要dispatch通过某种约定传递(例如,作为同步参数).

还要考虑一下这个代码将如何改变.假设我们想要第二个数据加载功能,并将它们组合到一个动作创建者中.

对于第一种方法,我们需要注意我们称之为什么样的行动创造者:

// action creators
function loadSomeData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(dispatch, userId) {
  return fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(dispatch, userId) {
  return Promise.all(
    loadSomeData(dispatch, userId), // pass dispatch first: it's async
    loadOtherData(dispatch, userId) // pass dispatch first: it's async
  );
}


// component
componentWillMount() {
  loadAllData(this.props.dispatch, this.props.userId); // pass dispatch first
}

有了Redux Thunk,动作创作者可以看到其他动作创作者的结果,甚至不考虑这些结果是同步的还是异步的:

// action creators
function loadSomeData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
    );
}
function loadOtherData(userId) {
  return dispatch => fetch(`http://data.com/${userId}`)
    .then(res => res.json())
    .then(
      data => dispatch({ type: 'LOAD_OTHER_DATA_SUCCESS', data }),
      err => dispatch({ type: 'LOAD_OTHER_DATA_FAILURE', err })
    );
}
function loadAllData(userId) {
  return dispatch => Promise.all(
    dispatch(loadSomeData(userId)), // just dispatch normally!
    dispatch(loadOtherData(userId)) // just dispatch normally!
  );
}


// component
componentWillMount() {
  this.props.dispatch(loadAllData(this.props.userId)); // just dispatch normally!
}

使用这种方法,如果以后希望动作创建者查看当前的Redux状态,只需使用传递给thunks的第二个getState参数,而无需修改调用代码:

function loadSomeData(userId) {
  // Thanks to Redux Thunk I can use getState() here without changing callers
  return (dispatch, getState) => {
    if (getState().data[userId].isLoaded) {
      return Promise.resolve();
    }

    fetch(`http://data.com/${userId}`)
      .then(res => res.json())
      .then(
        data => dispatch({ type: 'LOAD_SOME_DATA_SUCCESS', data }),
        err => dispatch({ type: 'LOAD_SOME_DATA_FAILURE', err })
      );
  }
}

如果需要将其更改为同步,也可以在不更改任何调用代码的情况下执行此操作:

// I can change it to be a regular action creator without touching callers
function loadSomeData(userId) {
  return {
    type: 'LOAD_SOME_DATA_SUCCESS',
    data: localStorage.getItem('my-data')
  }
}

因此,使用Redux Thunk或Redux Promise等中间件的好处是,组件不知道动作创建者是如何实现的,也不知道它们是否关心Redux状态,它们是同步还是异步的,以及它们是否调用其他动作创建者.缺点是有点间接,但我们相信它在实际应用中是值得的.

最后,Redux Thunk和friends只是Redux应用程序中异步请求的一种可能方法.另一个有趣的方法是Redux Saga,它允许您定义长时间运行的守护进程("saga"),这些守护进程在执行操作时执行操作,并在输出操作之前转换或执行请求.这将逻辑从动作创造者转移到传奇故事中.你可能想看看,然后 Select 最适合你的.

我搜索了Redux repo寻找线索,发现动作创作者在过go 被要求是纯功能的.

这是不正确的.doctor 是这么说的,但doctor 说错了.
操作创建者永远不需要是纯函数.
我们修正了文档以反映这一点.

Javascript相关问答推荐

如何在alpinejs中显示dev中x-for的元素

Angular material 表多个标题行映射

在NextJS中使用计时器循环逐个打开手风琴项目?

React对话框模式在用户单击预期按钮之前出现

在页面上滚动 timeshift 动垂直滚动条

在react js中使用react—router—dom中的Link组件,分配的右侧不能被 destruct ''

为什么promise对js中的错误有一个奇怪的优先级?

你怎么看啦啦队的回应?

我在我的Java代码中遇到了问题,代码的一部分看不到先前定义的对象

虚拟滚动实现使向下滚动可滚动到末尾

编辑文本无响应.onClick(扩展脚本)

为什么客户端没有收到来自服务器的响应消息?

Reaction Redux&Quot;在派单错误中检测到状态Mutations

元素字符串长度html

与svg相反;S getPointAtLength(D)-我想要getLengthAtPoint(x,y)

JavaScript:如果字符串不是A或B,则

为列表中的项目设置动画

如何在AG-Grid文本字段中创建占位符

如何正确地在ComponentWillUnmount中卸载状态以避免内存泄漏?

如何将值从后端传递到前端