Redux 用以前的状态覆盖模型
Redux overwrites model with previous state
我目前正在 AngularJs 结合 Redux 制作示例项目。
我正在努力让 reducer 的映射正常工作。
我有一个简单的输入,用户可以在其中设置一个新名称以及下拉到 select 和 'company'。
<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]
}
我遇到的问题是,当成功获取属性时,名称和 selected 公司被空值覆盖。我明白了为什么这些值被空值覆盖了,我找到了一种让它工作的方法。
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 网站,但我没有遇到任何其他解决方案。
一种选择是使用 Immutable,这会将您的减速器更改为:
case GET_COMPANIES:
return state.setIn(['list', 'loading'], true);
// etc
有关此方法的详细信息,请参阅 Using Immutable.JS with Redux。
另一种选择是使用Lodash, as shown in this Issue,您可以定义以下函数使其类似于不可变函数:
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 方法只使用普通对象,因此它可能比 Immutable 更容易理解。
破损可能是减速机的结构问题。它涉及太多不同的状态部分,并且必须对深层嵌套对象进行操作,因此很容易意外地改变状态。 guidelines for reducer structure 说将 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,
// ...
})
这样,每个化简器只关注一个事物或一组事物,其化简处理程序可以对 single-level 状态进行简单操作,而不是对深层嵌套状态进行复杂操作。
此外,在您的 list
子状态中,您似乎在 all
和 filtered
中存储了相同类型的集合资源,并且可能存在重叠。这导致相同数据的多个真实来源,从而为数据不一致打开了大门。相反,保留一个 filteredIds 数组:
export const filteredIds = (initialIds = [], { type, filteredIds }) => {
switch(type) {
case SET_FILTERED_IDS: return filteredIds;
default: return initialIds;
}
}
然后,使用按 filteredIds
过滤 all
的选择器来获取过滤后的项目。
我目前正在 AngularJs 结合 Redux 制作示例项目。 我正在努力让 reducer 的映射正常工作。
我有一个简单的输入,用户可以在其中设置一个新名称以及下拉到 select 和 'company'。
<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]
}
我遇到的问题是,当成功获取属性时,名称和 selected 公司被空值覆盖。我明白了为什么这些值被空值覆盖了,我找到了一种让它工作的方法。
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 网站,但我没有遇到任何其他解决方案。
一种选择是使用 Immutable,这会将您的减速器更改为:
case GET_COMPANIES:
return state.setIn(['list', 'loading'], true);
// etc
有关此方法的详细信息,请参阅 Using Immutable.JS with Redux。
另一种选择是使用Lodash, as shown in this Issue,您可以定义以下函数使其类似于不可变函数:
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 方法只使用普通对象,因此它可能比 Immutable 更容易理解。
破损可能是减速机的结构问题。它涉及太多不同的状态部分,并且必须对深层嵌套对象进行操作,因此很容易意外地改变状态。 guidelines for reducer structure 说将 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,
// ...
})
这样,每个化简器只关注一个事物或一组事物,其化简处理程序可以对 single-level 状态进行简单操作,而不是对深层嵌套状态进行复杂操作。
此外,在您的 list
子状态中,您似乎在 all
和 filtered
中存储了相同类型的集合资源,并且可能存在重叠。这导致相同数据的多个真实来源,从而为数据不一致打开了大门。相反,保留一个 filteredIds 数组:
export const filteredIds = (initialIds = [], { type, filteredIds }) => {
switch(type) {
case SET_FILTERED_IDS: return filteredIds;
default: return initialIds;
}
}
然后,使用按 filteredIds
过滤 all
的选择器来获取过滤后的项目。