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

时间:2016-03-02 12:28:19

标签: javascript reactjs redux react-redux

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

我已经创建了一个商店,我已经创建了我的第一个减速器功能,但现在我想到了一些问题,请帮助我理解。

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

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

这个具有单个对象树的redux模型是否能够在大型应用程序中很好地维持状态?如果我有1000个动作,我的减速器功能中的开关将是巨大的!我们怎么能避免这种情况?

谢谢!

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;

4 个答案:

答案 0 :(得分:5)

考虑一下:

创建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;

答案 1 :(得分:3)

首先,您可以在reducer中获取数据,因为它需要通过redux定义纯粹。您应该创建动作创建器,它将异步获取数据并将其传递给reducer。行动可能不纯。

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

此外,您可以使用redux-thunk之类的中间件来简化此操作。 https://github.com/gaearon/redux-thunk

至于第二个问题,您的应用中可以有多个reducer。而不是将它们与combineReducers(...)函数http://redux.js.org/docs/basics/Reducers.html

结合起来

答案 2 :(得分:2)

正如redux documentation所说,reducers应该是纯函数,因此它不应该执行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你的动作创建者会是这样的:

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});
    });
  }
}

你的减速机将成为纯粹的功能:

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

在这种情况下,您还可以处理加载状态,即在loadingaction.type时将您所在州的FIND_PRODUCT_STARTED标记设置为true。

在我的例子中,我假设findProductsByProductType返回Promise。在这种情况下,您甚至可以使用redux-promise-middleware而不使用redux-thunk,它将为您完成所有工作:

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

答案 3 :(得分:1)

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

相反,您应该使用操作创建者来分派请求API中的数据的操作。您的API中间件将返回一个承诺,即在完成时将调度具有有效负载的操作意图,以便减速器使用并使其返回下一个状态。

我建议您查看Redux ThunkRedux API middleware