根据文档,“没有中间件,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不能返回函数而不是操作。这只是事实。这就是人们使用Thunk的原因。阅读以下14行代码,了解它如何允许异步循环与一些附加的函数分层一起工作:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

https://github.com/reduxjs/redux-thunk

其他回答

简短的回答:对我来说,这似乎是解决异步问题的一种完全合理的方法。

当我在一个新项目上工作时,我有一个非常相似的想法,我们刚刚开始我的工作。我是vanilla Redux优雅系统的忠实粉丝,该系统可以更新商店并以一种远离React组件树的方式重新渲染组件。在我看来,使用这种优雅的调度机制来处理异步似乎很奇怪。

我最终采用了一种非常类似的方法,就像我从项目中分解出来的一个库一样,我们称之为react redux控制器。

出于以下几个原因,我最终没有采用上述方法:

按照您编写的方式,这些调度功能无法访问商店。通过让UI组件传递调度函数所需的所有信息,您可以在某种程度上绕过这个问题。但我认为这将不必要地将这些UI组件耦合到调度逻辑。更有问题的是,调度函数没有明显的方法来访问异步延续中的更新状态。分派函数可以通过词法范围访问分派本身。一旦connect语句失控,这就限制了重构的选项——而且仅使用一个更新方法看起来非常笨拙。因此,如果您将这些分派器函数分解为单独的模块,您需要一些系统来组合它们。

总之,您必须装配一些系统,以便将调度和存储以及事件的参数注入到调度函数中。我知道三种合理的依赖注入方法:

redux thunk通过将它们传递给你的thunk(根据dome定义,使它们完全不是thunk),以一种功能性的方式实现了这一点。我还没有使用过其他调度中间件方法,但我认为它们基本相同。react-redux控制器通过协程实现这一点。另外,它还允许您访问“选择器”,这些函数可能是您作为连接的第一个参数传入的,而不必直接使用原始的标准化存储。您还可以通过各种可能的机制将它们注入到这个上下文中,以面向对象的方式来实现这一点。

使现代化

我想到这个难题的一部分是react redux的限制。connect的第一个参数获取状态快照,但不获取分派。第二个参数得到分派,而不是状态。这两个参数都没有获得关闭当前状态的thunk,因为在继续/回调时能够看到更新的状态。

回答问题:

为什么容器组件不能调用异步API,然后调度行动?

我认为至少有两个原因:

第一个原因是关注点分离,调用api并获取数据不是动作创建者的工作,您必须向动作创建者函数传递两个参数,即动作类型和有效载荷。

第二个原因是因为redux存储正在等待一个具有强制操作类型和可选有效负载的普通对象(但这里也必须传递有效负载)。

动作创建者应该是一个简单的对象,如下所示:

function addTodo(text) {
  return {
    type: ADD_TODO,
    text
  }
}

Redux Thunk middleware的任务是将api调用的结果显示给适当的操作。

Redux不能返回函数而不是操作。这只是事实。这就是人们使用Thunk的原因。阅读以下14行代码,了解它如何允许异步循环与一些附加的函数分层一起工作:

function createThunkMiddleware(extraArgument) {
  return ({ dispatch, getState }) => (next) => (action) => {
    if (typeof action === 'function') {
      return action(dispatch, getState, extraArgument);
    }

    return next(action);
  };
}

const thunk = createThunkMiddleware();
thunk.withExtraArgument = createThunkMiddleware;

export default thunk;

https://github.com/reduxjs/redux-thunk

有同步动作创建器,然后有异步动作创建器。

同步动作创建器是一个当我们调用它时,它会立即返回一个action对象,其中包含附加到该对象的所有相关数据,并准备好由我们的还原器处理。

异步动作创建器需要一点时间才能最终调度动作。

根据定义,每当您有一个动作创建者发出网络请求时,它总是符合异步动作创建者的资格。

如果您想在Redux应用程序中使用异步动作创建器,则必须安装一种叫做中间件的东西,该中间件将允许您处理这些异步动作创建。

您可以在错误消息中验证这一点,该错误消息告诉我们使用自定义中间件执行异步操作。

那么什么是中间件,为什么我们需要它来实现Redux中的异步流呢?

在redux中间件(如redux thunk)的上下文中,中间件帮助我们处理异步动作创建者,因为这是redux无法开箱即用的。

随着中间件集成到Redux循环中,我们仍在调用动作创建者,这将返回一个将被调度的动作,但现在,当我们调度一个动作时,而不是直接将其发送给所有的reducer,我们将说一个动作将通过应用程序中的所有不同中间件发送。

在一个Redux应用程序中,我们可以拥有任意数量的中间件。在大多数情况下,在我们工作的项目中,我们将有一两个中间件连接到我们的Redux商店。

中间件是一个普通的JavaScript函数,我们分派的每个动作都会调用它。在该功能内部,中间件有机会阻止某个动作被分派到任何一个reducer,它可以修改某个动作或以任何方式处理某个动作,例如,我们可以创建一个中间件,控制台记录您分派的每一个动作,只是为了您的观看乐趣。

有大量的开源中间件可以作为依赖项安装到项目中。

您不限于仅使用开源中间件或将其作为依赖项安装。您可以编写自己的定制中间件,并在Redux商店中使用它。

中间件的一个更流行的用途(并得到您的答案)是处理异步动作创建者,可能最流行的中间件是redux thunk,它是帮助您处理异步动作创作者。

还有许多其他类型的中间件也可以帮助您处理异步动作创建者。

好了,让我们先来看看中间件是如何工作的,这很好地回答了这个问题,这是Redux中的源代码applyMiddleWare函数:

function applyMiddleware() {
  for (var _len = arguments.length, middlewares = Array(_len), _key = 0; _key < _len; _key++) {
    middlewares[_key] = arguments[_key];
  }

  return function (createStore) {
    return function (reducer, preloadedState, enhancer) {
      var store = createStore(reducer, preloadedState, enhancer);
      var _dispatch = store.dispatch;
      var chain = [];

      var middlewareAPI = {
        getState: store.getState,
        dispatch: function dispatch(action) {
          return _dispatch(action);
        }
      };
      chain = middlewares.map(function (middleware) {
        return middleware(middlewareAPI);
      });
      _dispatch = compose.apply(undefined, chain)(store.dispatch);

      return _extends({}, store, {
        dispatch: _dispatch
      });
    };
  };
}

看看这一部分,看看我们的调度是如何成为一个函数的。

  ...
  getState: store.getState,
  dispatch: function dispatch(action) {
  return _dispatch(action);
}

请注意,每个中间件都将作为命名参数提供dispatch和getState函数。

好了,这就是Redux thunk作为Redux最常用的中间件之一介绍自己的方式:

Redux Thunk中间件允许您编写返回函数而不是动作。thunk可用于延迟动作的调度,或仅在某个条件为遇见。内部函数接收存储方法分派和getState作为参数。

正如你所看到的,它将返回一个函数而不是一个操作,这意味着你可以随时等待并调用它,因为它是一个函数。。。

那他妈的是什么?维基百科就是这样介绍的:

在计算机编程中,thunk是一种用于注入将附加计算转换为另一个子例程。雷电主要是用于将计算延迟到需要时,或插入在另一个子例程的开始或结束处的操作。他们有用于编译器代码生成和模块化编程。这个词起源于“思考”的一个滑稽派生词。thunk是一个包装表达式以延迟其评价

//calculation of 1 + 2 is immediate 
//x === 3 
let x = 1 + 2;

//calculation of 1 + 2 is delayed 
//foo can be called later to perform the calculation 
//foo is a thunk! 
let foo = () => 1 + 2;

所以,看看这个概念有多简单,它如何帮助您管理异步操作。。。

这是你可以不用它生活的东西,但请记住,在编程中,总是有更好、更整洁和正确的方式来做事情。。。