我有一个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;
答案 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;
...
在这种情况下,您还可以处理加载状态,即在loading
为action.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中间件将返回一个承诺,即在完成时将调度具有有效负载的操作意图,以便减速器使用并使其返回下一个状态。