在我的应用程序中,我有很多模块在同一个文件中组合常量,动作创建器和缩减器以减少文件碎片,它们看起来像这样:
/**
* Store
* Products
*/
import { Api } from './api';
// Constants
const REQUEST_PRODUCTS = 'REQUEST_PRODUCTS';
const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS';
// Action creators
export function requestProducts() {
return { type: REQUEST_PRODUCTS };
}
export function receiveProducts(json) {
return {
type: RECEIVE_PRODUCTS,
entities: json.results,
receivedAt: Date.now()
};
}
function fetchProducts() { // Thunk
return function (dispatch, getState) {
dispatch(requestProducts());
return Api.get('/products/')
.then(json => dispatch(receiveProducts(json)))
}
}
// Reducer
export function reducer(state = {
isFetching: false,
didInvalidate: false,
entities: []
}, action = '') {
switch (action.type) {
case REQUEST_PRODUCTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
});
case RECEIVE_PRODUCTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
entities: action.entities,
lastUpdated: action.receivedAt
});
default:
return state
}
}
在我的商店配置中,我然后导入这样的reducer:
import { combineReducers } from 'redux';
import { routeReducer } from 'redux-simple-router'
const rootReducer = combineReducers({
routing: routeReducer,
products: require('../modules/products').reducer,
blah: require('../modules/blah').reducer,
});
export default rootReducer;
从组件中我导入了这样的动作:
import { fetchProducts } from '../modules/products';
dispatch( fetchProducts() );
除了动作创建者函数中的常量名称和实体名称之外,所有“store”文件都完全相同。正如诫命所说:不要重复自己,这是重构的理想选择,因此我正在尝试创建一个我可以用来消除重复的工厂,并且仍然对每个“商店”进行一定程度的定制。但鉴于我有限的Javascript经验,特别是在ES6模块语法方面,我很难找到一个不错的解决方案。
我可以尝试任何想法吗?
答案 0 :(得分:4)
减速剂成分是关键。通过抽象代码的重复部分来创建reducer。我们返回一个类似于已经用于products
等的reducer函数;但是,我们传递key
(字符串),以便在我们州的根目录中我们可以跟踪和更新products
,blah
等。如果您不熟悉传播操作符,则与做Object.assign({}, state, {state[key]: {updates}});
类似。
function entities (key) {
return function entitiesByKey (state, action) {
switch (action.type) {
case REQUEST:
return {
...state,
state[key]: {
isFetching: true
}
};
case RECEIVE:
return {
...state,
state[key]: {
entities
}
};
}
}
}
然后弄清楚如何将它与当前的根减速器一起使用:
combineReducers({
products: entities('products'),
blah: entities('blah')
});
这应该会产生一个与此类似的状态树(一个不完整的例子):
{
products: {
isFetching: false,
entities: {}
},
blah: {}
}
我根本没有测试过这段代码,但概念就在那里。
就真实世界的示例和API中间件而言,它实际上非常简单。他正在通过具有API信息(url,常量等)的动作创建者传递对象([CALL_API]
)。当它到达中间件时,他提取[CALL_API]
对象并初始化文字API调用(使用fetch()
),然后根据响应正确地重新格式化操作对象。 [CALL_API]
看起来令人困惑,但它是使用Symbol()
作为对象键的语法:
export const MY_SYMBOL = Symbol('My Symbol');
{
[MY_SYMBOL]: {
prop: "value"
}
}
原因是因为符号是唯一值,因此无法覆盖[CALL_API]
,就像它是一个字符串一样。
答案 1 :(得分:1)
鉴于你有多个这些东西,我建议把它们变成对象而不是模块。这使得它们更容易以编程方式(比如模块代码)进行操作,并且也可以存储在数据结构中:
const REQUEST_PRODUCTS = 'REQUEST_PRODUCTS';
const RECEIVE_PRODUCTS = 'RECEIVE_PRODUCTS';
export default {
// Action creators
requestProducts() {
return { type: REQUEST_PRODUCTS };
},
receiveProducts(json) {
return {
type: RECEIVE_PRODUCTS,
entities: json.results,
receivedAt: Date.now()
};
},
fetchProducts() { // Thunk
return (dispatch, getState) => {
dispatch(this.requestProducts());
return Api.get('/'+type+'/')
.then(json => dispatch(this.receiveProducts(json)))
}
},
// Reducer
reducer(state = {
isFetching: false,
didInvalidate: false,
entities: []
}, action = '') {
switch (action.type) {
case REQUEST_PRODUCTS:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
});
case RECEIVE_PRODUCTS:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
entities: action.entities,
lastUpdated: action.receivedAt
});
default:
return state
}
}
};
那很简单。但是现在我们有一个 thing - 一个JavaScript值,我们可以很容易地将它包装在一个返回它的函数中。
然后我们需要做的就是删除特定于实例的部分(在这种情况下,"产品"就足够了),并用传递给函数的参数替换它们:
function moduleFactory(type) {
const REQUEST = 'REQUEST_'+type.toUpperCase();
const RECEIVE = 'RECEIVE_'+type.toUpperCase();
return {
// Action creators
request() {
return { type: REQUEST };
},
receive(json) {
return {
type: RECEIVE,
entities: json.results,
receivedAt: Date.now()
};
},
fetch() { // Thunk
return (dispatch, getState) => {
dispatch(this.request());
return Api.get('/'+type+'/')
.then(json => dispatch(this.receive(json)))
}
},
// Reducer
reducer(state = {
isFetching: false,
didInvalidate: false,
entities: []
}, action = '') {
switch (action.type) {
case REQUEST:
return Object.assign({}, state, {
isFetching: true,
didInvalidate: false
});
case RECEIVE:
return Object.assign({}, state, {
isFetching: false,
didInvalidate: false,
entities: action.entities,
lastUpdated: action.receivedAt
});
default:
return state
}
}
};
}
你完成了!现在你可以对此进行优化,例如通过分享不依赖于多个模块之间类型的方法,但这并不重要。
import moduleFactory from '…';
const rootReducer = combineReducers({
routing: routeReducer,
products: moduleFactory('products').reducer,
blah: moduleFactory('blah').reducer,
});