如何创建常量工厂、action creators 和 reducers?
How to create a factory of constants, action creators and reducers?
在我的应用程序中,我有很多模块将常量、action creators 和 reducers 分组在同一个文件中以减少文件碎片,它们看起来像这样:
/**
* 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
}
}
在我的商店配置中,我然后像这样导入减速器:
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" 文件完全相同,除了动作创建者函数中的常量名称和实体名称。正如诫命所说:不要重复自己,这是重构的理想人选,所以我正在尝试创建一个工厂,我可以用它来消除重复,并且仍然对每个 "store" 进行一定程度的定制。但是考虑到我有限的 Javascript 经验,特别是在 ES6 模块语法方面,我很难想出一个像样的解决方案。
我有什么想法可以尝试吗?
Reducer 组合是这里的关键。通过抽象代码的重复部分来创建一个 reducer。我们 return 一个类似于已经用于 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]
。
鉴于您有多个这样的东西,我建议将它们设为对象而不是模块。这只会让它们更容易以编程方式进行操作(比说,模块代码),也更容易存储在数据结构中:
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 值,我们可以很容易地将它包装在一个 returns this.
的函数中
然后我们需要做的就是删除特定于实例的部分(在这种情况下,"products" 就足够了),并用传递给函数的参数替换它们:
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,
});
在我的应用程序中,我有很多模块将常量、action creators 和 reducers 分组在同一个文件中以减少文件碎片,它们看起来像这样:
/**
* 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
}
}
在我的商店配置中,我然后像这样导入减速器:
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" 文件完全相同,除了动作创建者函数中的常量名称和实体名称。正如诫命所说:不要重复自己,这是重构的理想人选,所以我正在尝试创建一个工厂,我可以用它来消除重复,并且仍然对每个 "store" 进行一定程度的定制。但是考虑到我有限的 Javascript 经验,特别是在 ES6 模块语法方面,我很难想出一个像样的解决方案。
我有什么想法可以尝试吗?
Reducer 组合是这里的关键。通过抽象代码的重复部分来创建一个 reducer。我们 return 一个类似于已经用于 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]
。
鉴于您有多个这样的东西,我建议将它们设为对象而不是模块。这只会让它们更容易以编程方式进行操作(比说,模块代码),也更容易存储在数据结构中:
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 值,我们可以很容易地将它包装在一个 returns this.
的函数中然后我们需要做的就是删除特定于实例的部分(在这种情况下,"products" 就足够了),并用传递给函数的参数替换它们:
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,
});