1

我目前正在结合 Redux 在 AngularJs 中制作一个示例项目。我正在努力从减速器工作中获取映射。

我有一个简单的输入,用户可以在其中设置新名称以及下拉菜单以选择“公司”。

<input type="text" ng-model="$ctrl.single.object.name">
<select ng-change="$ctrl.getProperties()"
ng-options="option.description as option.description for option in $ctrl.list.all"
ng-model="$ctrl.single.object.company">

当用户更改公司时,需要获取新属性以便用户设置这些属性。

function FooController($ngRedux, FooActions, BarActions) {
    this.$onInit = function () {
        this.unsubscribeCompanies = $ngRedux.connect(this.mapStateToThis, BarActions)(this);
        this.fetchCompanyList();
    };

    this.$onDestroy = function () {
        this.unsubscribeCompanies();
    };

    this.fetchCompanyList = function () {
        this.fetchCompanies().payload.then((response) => {
            this.fetchCompaniesSuccess(response.data);
        }, (error) => {
            this.fetchCompaniesError(error.data);
        });
    };

    this.getProperties = function () {
        this.fetchCompanyProperties(this.single.object.company).payload.then((response) => {
            this.fetchCompanyPropertiesSuccess(response.data);
        }, (error) => {
            this.fetchCompanyPropertiesError(error.data);
        });
    };

    this.mapStateToThis = function (state) {
        return {
            list: state.bar.list,
            single: state.bar.single
        };
    };
}

module.exports = {
    template: require('./index.html'),
    controller: ['$ngRedux', 'FooActions', 'BarActions', FooController]
}

我得到的问题是,当获取属性成功时,名称和所选公司被空值覆盖。我明白为什么这些值会被空值覆盖,并且我找到了一种让它工作的方法。

export const GET_COMPANIES = 'GET_COMPANIES';
export const GET_COMPANIES_SUCCESS = 'GET_COMPANIES_SUCCESS';
export const GET_COMPANIES_ERROR = 'GET_COMPANIES_ERROR';
export const GET_COMPANIES_PROPERTIES = 'GET_COMPANIES_PROPERTIES';
export const GET_COMPANIES_PROPERTIES_SUCCESS = 'GET_COMPANIES_PROPERTIES_SUCCESS';
export const GET_COMPANIES_PROPERTIES_ERROR = 'GET_COMPANIES_PROPERTIES_ERROR';

export default function BarActions($http) {
    function fetchCompanies() {
        return {
            type: GET_COMPANIES,
            payload: $http.get('api/companies')
        };
    }

    function fetchCompaniesSuccess(companies) {
        return {
            type: GET_COMPANIES_SUCCESS,
            payload: companies
        };
    }

    function fetchCompaniesError(error) {
        return {
            type: GET_COMPANIES_ERROR,
            payload: error
        };
    }
    function fetchCompanyProperties(company) {
        return {
            type: GET_COMPANIES_PROPERTIES,
            payload: $http.get(`api/company/${company}/properties`)
        };
    }

    function fetchCompanyPropertiesSuccess(properties) {
        return {
            type: GET_COMPANIES_PROPERTIES_SUCCESS,
            payload: properties
        };
    }

    function fetchCompanyPropertiesError(error) {
        return {
            type: GET_COMPANIES_PROPERTIES_ERROR,
            payload: error
        };
    }

    return {
        fetchCompanies,
        fetchCompaniesSuccess,
        fetchCompaniesError,
        fetchCompanyProperties,
        fetchCompanyPropertiesSuccess,
        fetchCompanyPropertiesError
    }
}

我覆盖reducer中的值的方式如下:

import { GET_COMPANIES, GET_COMPANIES_SUCCESS, GET_COMPANIES_ERROR, GET_COMPANIES_PROPERTIES, GET_COMPANIES_PROPERTIES_ERROR, GET_COMPANIES_PROPERTIES_SUCCESS } from "../actions/bar.actions";

const all = [];

const initialState = {
    list: {
        all,
        filtered: all,
        error: null,
        loading: false
    },
    single: {
        object: {},
        error: null,
        loading: false
    }
};

export function BarReducer(state = initialState, action) {
    switch (action.type) {
        case GET_COMPANIES:
            return { ...state, list: { all: [], filtered: [], error: null, loading: true } };
        case GET_COMPANIES_SUCCESS:
            return { ...state, list: { all: action.payload, filtered: action.payload, error: null, loading: false } };
        case GET_COMPANIES_ERROR:
            return { ...state, list: { all: [], filtered: [], error: action.payload.innerException, loading: false } };
        case GET_COMPANIES_PROPERTIES:
            return { ...state, single: { ...state.single, object: { ...state.single.object }, error: null, loading: true } };
        case GET_COMPANIES_PROPERTIES_SUCCESS:
            return { ...state, single: { ...state.single, object: { ...state.single.object, payloadValues: action.payload }, error: null, loading: false } };
        case GET_COMPANIES_PROPERTIES_ERROR:
            return { ...state, single: { object: null, error: action.payload.innerException, loading: false } };
        default:
            return state;
    }
}

我现在使用扩展运算符覆盖旧状态的方式感觉很脏。我想知道是否有任何规则或指南来处理这个问题。到目前为止,我已经在互联网上搜索了一段时间,特别是 Redux 网站,但我没有遇到任何其他解决方案。

4

2 回答 2

2

破损很可能是由于减速器的结构造成的。它关心状态的太多不同部分,并且必须对深层嵌套对象进行操作,从而很容易意外地改变状态。reducer 结构的指导方针说,将 reducer 状态拆分为规范化切片是最好的方法。

尝试将您的一个减速器拆分为多个较小的减速器。例如:

export const all = (initialAll = [], { type, companies }) => {
    switch(type) {
        case GET_COMPANIES_SUCCESS: return companies;
        default: return initialAll;
    }
}

export const error = (initialError = '', { type, error }) => {
    switch(type) {
        case GET_COMPANIES_ERROR: return error;
        default: return initialError;
    }
}

export const isFetching = (isFetching = false, { type }) => {
    switch(type) {
        case GET_COMPANIES: return true;
        case GET_COMPANIES_SUCCESS: return false;
        case GET_COMPANIES_ERROR: return false;
        default: return isFetching;
    }
}

然后,将它们组合成一个 reducer:

import { combineReducers } from 'redux';

export list = combineReducers({
    all,
    error,
    isFetching
});

// ...
export rootReducer = combineReducers({
    list,
    single,
    // ...
})

这样,每个 reducer 只关心一个事物或一组事物,并且它的 reduce 处理程序可以对单级状态进行简单的操作,而不是对深度嵌套状态进行复杂的操作。

此外,在您的list子状态中,您似乎在两者中存储了相同类型的集合资源,all并且filtered可能存在重叠。这导致同一数据有多个真实来源,从而为数据不一致打开了大门。相反,保留一组过滤后的 ID:

export const filteredIds = (initialIds = [], { type, filteredIds }) => {
    switch(type) {
        case SET_FILTERED_IDS: return filteredIds;
        default: return initialIds;
    }
}

然后,使用按 过滤的选择器allfilteredIds获取您过滤的项目。

于 2018-04-05T16:12:16.977 回答
0

一种选择是使用Immutable,这会将您的减速器更改为:

case GET_COMPANIES:
    return state.setIn(['list', 'loading'], true);
// etc

有关此方法的更多信息,请参阅将 Immutable.JS 与 Redux一起使用。

另一种选择是使用Lodash,如本期所示,您可以定义以下函数以使其类似于不可变的函数:

import {clone, setWith, curry} from 'lodash/fp';

export const setIn = curry((path, value, obj) =>
  setWith(clone, path, value, clone(obj)),
);

然后你可以使用setIn如下:

case GET_COMPANIES:
    return setIn(['list', 'loading'], true, state);
// etc

Lodash 方法只是使用普通对象,所以它可能比不可变更容易理解。

于 2018-04-05T14:27:49.140 回答