如何创建常量,动作创建者和缩减器的工厂?

时间:2016-02-03 16:46:43

标签: javascript reactjs redux

在我的应用程序中,我有很多模块在同一个文件中组合常量,动作创建器和缩减器以减少文件碎片,它们看起来像这样:

    /**
     * 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模块语法方面,我很难找到一个不错的解决方案。

我可以尝试任何想法吗?

2 个答案:

答案 0 :(得分:4)

减速剂成分是关键。通过抽象代码的重复部分来创建reducer。我们返回一个类似于已经用于products等的reducer函数;但是,我们传递key(字符串),以便在我们州的根目录中我们可以跟踪和更新productsblah等。如果您不熟悉传播操作符,则与做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,
});