Redux:reducer 函数只允许同步调用吗?

Redux: Are only synchronous calls allowed from reducer functions?

我有一个 reactJs 应用程序,现在我正在学习 Redux 以将其用作 Flux 实现。

我已经创建了一个 store 并且我已经创建了我的第一个 reducer 函数,但是现在我有一些问题出现在我的脑海中,请帮助我理解。

如您所见,我有一个名为 'FIND_PRODUCTS' 的操作,它基本上是从后端服务获取数据。要调用此后端服务,我基本上使用异步 ajax 调用,所以基本上我面临的问题是状态是在我的后端调用完成之前从 reducer 函数返回的,因此状态未正确更新商店的订阅者得到的数据不正确。如果我切换到同步调用,这个问题就解决了,但是,我得到的第一个警告是应该避免同步调用,因为它可能会降低用户的体验(性能)。

所以我的问题是,我们只能从 reducer 函数中同步获取数据吗? 获取数据应该发生在 reducer 函数中还是有另一种方法来做到这一点?如果有,那是什么?

这种使用单个对象树来维护状态的 redux 模型是否适用于大型应用程序?如果我有 1000 个动作,我的 reducer 函数中的开关将会很大!我们怎样才能避免这种情况?

谢谢!!

const initialState = {
availableLocales: [{text: 'En'}, {text: 'Es'}, {text: 'Fr'}],
selectedLocale: 'En',
translations: i18n.getTranslations(),
products: []
};


const reducer = (state = initialState, action = {type: 'NONE'})=> {

//To make the reducer a pure function
deepFreeze(state);
deepFreeze(action);
switch (action.type) {
    case 'SWITCH_LOCALE':
        let newState = Object.assign({}, state, {
            selectedLocale: action.locale,
            translations: i18n.getTranslations(action.locale)
        });
        return newState;
    case 'FIND_PRODUCTS':
        let newState = Object.assign({}, state, {
            products:ProductHelper().findProductsByProductType(action.productType)
        });
        return newState;

    default:
        return state
}
return state;
}

// Create a Redux store holding the state of your app.
// Its API is { subscribe, dispatch, getState }.
const store = createStore(reducer);

// You can subscribe to the updates manually, or use bindings to your view  layer.
store.subscribe(() =>
    console.log(store.getState())
);

export default store;

首先,您不能在 reducer 中获取数据,因为根据 redux 定义,它需要是纯数据。您应该创建 action creator,它将异步获取数据并将其传递给 reducer。行动可能是不纯的。

在这里你可以阅读更多http://redux.js.org/docs/advanced/AsyncActions.html

您也可以使用像 redux-thunk 这样的中间件来简化它。 https://github.com/gaearon/redux-thunk


关于第二个问题,你的app中可以有多个reducer。然后将它们与 combineReducers(...) 函数组合 http://redux.js.org/docs/basics/Reducers.html

您不应在减速器中使用 ProductHelper() 来请求数据。

相反,您应该使用动作创建器来分派从您的 API 请求数据的动作。您的 API 中间件会 return 承诺在完成时会发送一个带有有效载荷的动作意图供您的减速器使用并使其进入 return 下一个状态。

我推荐你看看Redux Thunk and Redux API middleware

正如redux documentation 所说,reducer 应该是纯函数,所以它不应该做ajax 请求。

更好的方法是使用 redux-thunk 中间件,它允许您在一个操作中多次调用 dispatch

因此,在您的示例中,您执行如下操作:

// definition of action creator
function loadProducts(productType) {
  return {type: 'FIND_PRODUCTS', productType: productType}
}

...
// calling dispatch of your action
dispatch(loadProducts(productType));

但是使用 redux-thunk 你的 action creator 将是这样的:

function loadProducts(productType) {
  return function(dispatch){
    dispatch({type: 'FIND_PRODUCT_STARTED'});
    // I don'h know how findProductsByProductType works, but I assume it returns Promise
    ProductHelper().findProductsByProductType(productType).then(function(products){
      dispatch({type: 'FIND_PRODUCT_DONE', products: products});
    });
  }
}

你的 reducer 将变成纯函数:

...
case 'FIND_PRODUCTS_DONE':
  let newState = Object.assign({}, state, {
      products: action.products,
  });
  return newState;
...

在这种情况下,您还可以处理加载状态,即当 action.typeFIND_PRODUCT_STARTED 时,将状态中的 loading 标志设置为 true。

在我的示例中,我假设 findProductsByProductType returns Promise。在这种情况下,您甚至可以在没有 redux-thunk 的情况下使用 redux-promise-middleware,它会为您完成所有工作:

function loadProducts(productType) {
  return {
    type: 'FIND_PRODUCT',
    payload: {
      promise: ProductHelper().findProductsByProductType(productType)
    }
  }
}

考虑一下:

创建 actions.js 文件并导出动作函数,如下所示:

import * as types from '../constants/action_types';
import * as api from '../utils/api'

export function something1(someId){

    return (dispatch) => {

        dispatch({type: `${types.SOMETHING1}_PENDING`});

        api.getSomething(someId)

            .then((res) => {
                dispatch({
                    type: `${types.SOMETHING1}_SUCCEEDED`,
                    somethings: res.body
                });

            .catch((err) => {

                dispatch({
                    type: `${types.SOMETHING1}_FAILED`,
                    errors: err.body
                })

            });
    }
} 

export function something2(someOtherId){

    return (dispatch) => {

        dispatch({type: `${types.SOMETHING2}_PENDING`});

        api.getSomething2(someOtherId)

            .then((res) => {
                dispatch({
                    type: `${types.SOMETHING2}_SUCCEEDED`,
                    otherThings: res.body
                });

            .catch((err) => {

                dispatch({
                    type: `${types.SOMETHING2}_FAILED`,
                    errors: err.body
                })

            });
    }
} 

只有当你有数据时,状态才会改变

下一步将你的 reducer 分离到单独的文件中,并创建一个文件来导出它们 像这样 reducers/index.js:

export { default as reducer1 } from './reducer1';
export { default as reducer2 } from './reducer2';
export { default as reducer3 } from './reducer3';
export { default as reducer4 } from './reducer4';

然后像这样配置您的商店:

configure_store.js

import { createStore, combineReducers, applyMiddleware } from 'redux';
import thunk from 'redux-thunk';
import * as reducers from '../reducers';

const rootReducer = combineReducers(reducers);
const createStoreWithMiddleware = applyMiddleware(thunk)(createStore);

export default function configureStore(initialState) {
    return createStoreWithMiddleware(rootReducer, initialState);
}

最后将此添加到您的根目录:

import configureStore from '../store/configure_store';

const store = configureStore();

class Root extends Component {

    render() {
        return (
            ...
            <Provider store={ store } >
            ...
            </Provider>
        );
    }
}

export default Root;