我们应该在角度应用中规范状态吗?

时间:2018-08-09 08:42:50

标签: angular typescript ngrx ngrx-store

在我们公司,我们正在进行包括ngrx-store在内的第一个Angular项目,并且我们开始讨论是否应将状态规范化-这个问题的含义有所不同。

有人认为,嵌套存储将更易于维护/使用,因为api已经发送了嵌套数据,并且在某些情况下,应从存储中清除整个对象。

  

示例:假设我们有一个名为 customer 的功能存储。我们在其中存储客户列表和选定的客户。一点:选定的客户对象比客户列表中的客户对象具有更多的属性。

     

我们在组件中显示商店中的客户列表(型号: CustomerList ),如果用户单击某个条目,他将被重定向到详细信息页面,在该页面上,客户详细信息(型号:<显示strong> CustomerDetail )。

     

在详细信息页面内,用户可以创建/编辑/删除所有客户子列表​​(例如地址,电话,传真等)。   如果关闭了特定客户的详细信息页面,并且用户又回到了列表中,则应清除该客户的商店对象。

export interface CustomerState {
    customers: CustomerList[],
    customer: CustomerDetail
};

export interface CustomerList {
    customerId: number;
    name: string;
};

export interface CustomerDetail {
    customerId: number;
    firstName: string;
    lastName: string;
    addressList: CustomerDetailAddressList[];
    phoneList: CustomerDetailPhoneList[];
    emailList: CustomerDetailEmailList[];
    faxList: CustomerDetailFaxList[];
    /* ... */
};

如果现在有一个用户,比方说在特定客户详细信息页面上为客户创建了一个新地址,则该新地址将发布到api,并且在api成功响应后,商店会再次从api重新获取新的addressList并将其放入商店内客户的地址列表中。

有些人认为未规范状态的最大缺点如下:

  

嵌套越深,商店内部用于设置数据或从中获取数据的映射就越长且越复杂。

其他人认为,如果我们对状态进行规范化以便在客户对象内没有嵌套,那么我们将如何从中受益,因为在我们的特殊情况下,customerList-object和customerDetail-object彼此不同,否则,如果两个对象都相同,我们只需将所选客户的ID存储在商店中,然后按ID从客户列表中选择数据即可。

他们在讨论中带来的另一个缺点是,如果用户离开客户详细信息页面并且状态已规范化,则不仅清除客户对象,还清除了所有其他与客户有关的列表。

export interface CustomerState {
    customers: CustomerList[],
    customer: CustomerDetail,
    customerAddressList: CustomerDetailAddressList[];
    customerPhoneList: CustomerDetailPhoneList[];
    customerEmailList: CustomerDetailEmailList[];
    customerFaxList: CustomerDetailFaxList[];
};

tl; dr

你们的看法/您在商店的体验如何(无论是否经过规范化),我们如何才能从两全其美中获得最大收益?任何回应将不胜感激!

如果有什么不清楚的地方或者根本没有意义,请告诉我-我们仍在学习-任何帮助和/或建议都将受到高度赞赏。

1 个答案:

答案 0 :(得分:1)

您已经在问题中很好地讨论了这两种方法的利弊-这确实是两个选择之间的折衷-存储单个对象的好处意味着更新会在引用该对象的所有位置周围进行,但是它使您的减速器更加复杂。

在我们复杂的应用程序中,我们选择(主要是)使用嵌套选项,但是为了帮助保持reducer实现的简洁性,我们编写了纯函数运算符来更新(例如)Company:

function applyCompanyEdit(state: CompanyState, compId: number, compUpdate: (company: CompanyFull) => CompanyFull): CompanyState {
    let tab = state.openCompanies.find((aTab) => aTab.compId == compId);
    if (!tab) return state;
    let newCompany = compUpdate(tab.details);
    // Set company edited if the new company returned by compUpdate are not the exact same
    // object as the previous state.
    let compEdited = tab.edited || tab.details != newCompany;

    return {
        ...state,
        openCompanies: state.openCompanies.map((ct) => {
            if (ct.compId == compId) return {
                ...ct,
                edited: compEdited,
                details: newCompany
            };
            return ct;
        })
    };
}

...然后在多个化简操作中使用它来更新单个公司,如下所示:

case CompanyStateService.UPDATE_SHORTNAME: 
    return applyCompanyEdit(state, action.payload.compId, (comp: CompanyFull) => {
        return {
            ...comp,
            shortName: action.payload.shortName
        }
    });
case CompanyStateService.UPDATE_LONGNAME: 
    return applyCompanyEdit(state, action.payload.compId, (comp: CompanyFull) => {
        return {
            ...comp,
            longName: action.payload.longName
        }
    });

这对我们来说非常有效,因为减速器操作保持了非常整洁和易于理解,并且我们只需要编写笨拙的函数来查找正确的对象即可进行一次更新。在这种情况下,嵌套相对较浅,但是可以扩展到applyCompanyEdit函数中的任意深度结构,从而将复杂性保持在一个位置。