摘要:前言本文講述的數(shù)據(jù)模型并不是一個(gè)庫,也不是需要的包,僅僅只是一種在多人團(tuán)隊(duì)協(xié)作開發(fā)的時(shí)候擬定的規(guī)則。至少目前為止,我們的開發(fā)團(tuán)隊(duì)再也沒用過雖然一開始也沒用,也不用擔(dān)心后臺(tái)數(shù)據(jù)的字段或結(jié)構(gòu)發(fā)生變動(dòng),真正實(shí)現(xiàn)前后臺(tái)并行開發(fā)的愉快模式。
前言
本文講述的數(shù)據(jù)模型并不是一個(gè)庫,也不是需要npm的包,僅僅只是一種在多人團(tuán)隊(duì)協(xié)作開發(fā)的時(shí)候擬定的規(guī)則。至少目前為止,我們的開發(fā)團(tuán)隊(duì)再也沒用過mock(雖然一開始也沒用),也不用擔(dān)心后臺(tái)數(shù)據(jù)的字段或結(jié)構(gòu)發(fā)生變動(dòng),真正實(shí)現(xiàn)前后臺(tái)并行開發(fā)的愉快模式。
本文技術(shù)棧有 Typescript、Rxjs、AngularX定義Model
類比于java里的類,我們的Model也是一個(gè)類,是TS的類,我們根據(jù)需求和設(shè)計(jì)圖或原型圖規(guī)劃好某一個(gè)具體的模塊的基類Model,并自行定義一些字段和枚舉類型,方法屬性等,并不需要強(qiáng)行和后臺(tái)的字段一致,要保證百分百純的前后端分離,舉個(gè)例子
比如開發(fā)某一個(gè)后臺(tái)管理項(xiàng)目,里邊有產(chǎn)品(Product)模塊、用戶(User)模塊等
那么我們會(huì)在model文件夾里定義BaseProduct的基類
export class BaseProductModel { constructor() {} // 必有id 和 name public id: number = null; public name: string = ""; /...more.../ }
基類的定義是必要的,可以節(jié)省很多不必要的代碼,并不需要寫一個(gè)頁面或組件就重新定義新的model,如果某一個(gè)組件里面需要對(duì)這個(gè)產(chǎn)品的內(nèi)容進(jìn)行拓展的大可直接繼承,并不會(huì)影響其他有了這個(gè)基類的文件
我們推崇一切基類都必須繼承,不可直接構(gòu)造
真實(shí)的項(xiàng)目中產(chǎn)品的字段和屬性肯定不止只有id和name,可能還包含版本、縮略圖地址、唯一標(biāo)識(shí)、產(chǎn)品、對(duì)應(yīng)規(guī)格的價(jià)格、狀態(tài)、創(chuàng)建時(shí)間等等;這些屬性完全可以放在基類里,因?yàn)樗挟a(chǎn)品都有這些屬性,說到類型和狀態(tài)的定義,請(qǐng)注意
絕對(duì)不能將可枚舉性質(zhì)的屬性直接使用后臺(tái)或第三方返回的對(duì)應(yīng)屬性
比如,產(chǎn)品模塊里最基礎(chǔ)的狀態(tài)(status)屬性,假設(shè)后臺(tái)定義的對(duì)應(yīng)狀態(tài)有
0: 禁用 1: 啟用 2: 隱藏 3: 不可購(gòu)買
這四種,倘若我們?cè)陧?xiàng)目當(dāng)中直接使用這些對(duì)應(yīng)狀態(tài)的數(shù)字去判斷或進(jìn)行邏輯處理,分不分的清另談,如果中途或以后狀態(tài)的數(shù)字變了,GG??赡艽蠹矣X得這樣的情況很少,但也不是沒有,一旦出現(xiàn)改起來BUG就一堆。
所以對(duì)于這種可枚舉性質(zhì)的屬性我們會(huì)定義一個(gè)枚舉類(Enum)
export enum EStatus { BAN = 0, OPEN = 1, HIDE = 2, NOTBUY = 3 }
然后在model里這樣
export class BaseProductModel { // ...... public status: string = EStatus[1] // 默認(rèn)啟用 }
美滋滋,而且在進(jìn)行邏輯判斷的時(shí)候我們也不用去關(guān)心每個(gè)狀態(tài)對(duì)應(yīng)的數(shù)字是什么,我們只關(guān)心它是BAN還是OPEN,簡(jiǎn)潔明了不含糊
而且我們還可以給model增加一個(gè)只讀屬性,用來返回這個(gè)狀態(tài)對(duì)應(yīng)的中文提示(這種需求很常見)
public get conversionStatusHint() : string { const _ = { BAN: "禁用", OPEN: "啟用", HIDE: "隱藏", NOTBUY: "買不得呀" } return _[this.status] ? _[this.status] : "" }
這樣就不用在每一個(gè)組件里面寫一個(gè)方法來傳參數(shù)返回中文名稱了
到了這里,我們的BaseProductModel已經(jīng)算是定義好了,下面我們就需要給這個(gè)model定義一個(gè)方法
目的是把后臺(tái)返回的字段和數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化為我們自己定義的字段和數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)化后臺(tái)數(shù)據(jù)
可能到了這里很多人會(huì)覺得這是多此一舉,后臺(tái)都直接返回?cái)?shù)據(jù)了還轉(zhuǎn)化什么,返回什么用什么就得了。
但在大型的團(tuán)隊(duì)開發(fā)項(xiàng)目當(dāng)中,誰也不能保證一個(gè)字段也不修改,一個(gè)字段也不刪除或增加或缺失,牽一發(fā)動(dòng)全身。人生苦短。而且還有一種情況就是,可能這個(gè)項(xiàng)目是前端先進(jìn)行,后臺(tái)還未介入,需要前端這邊先把整體的功能和樣式都先根據(jù)設(shè)計(jì)圖規(guī)劃開發(fā)。
export class BaseProductModel { // ...... // 轉(zhuǎn)化后臺(tái)數(shù)據(jù) public setData( data: BaseProductModel ): void { if (data) { for (let e in this) { if ((
然后在調(diào)用的時(shí)候
/** 假設(shè)ProductModel類繼承了BaseProductModel類 */ public productModel: ProductModel = new ProductModel(); /...more.../ this.productModel.setData({ // 假設(shè)后臺(tái)定義的創(chuàng)建時(shí)間字段是create_at,model里定的創(chuàng)建時(shí)間是createTime createTime: data.create_at }); // 即使數(shù)據(jù)結(jié)構(gòu)不一致也可在這里進(jìn)行統(tǒng)一轉(zhuǎn)化
做好了轉(zhuǎn)化這一步,所有的數(shù)據(jù)變動(dòng)和數(shù)據(jù)結(jié)構(gòu)的變化都在這同一個(gè)地方修改即搞定,這個(gè)時(shí)候隨便后臺(tái)怎么改,歡樂改,都不影響我們后續(xù)的邏輯處理和字段的變動(dòng)。同理,在post數(shù)據(jù)給后臺(tái)的時(shí)候轉(zhuǎn)化就顯得容易多了,后臺(tái)需要什么數(shù)據(jù)和字段再轉(zhuǎn)化一次不就得了。
以上的數(shù)據(jù)模型可以很好的降低前后臺(tái)掐架的概率,mock?不需要
下面是一個(gè)我們抽離出來的常用的表格數(shù)據(jù)模型基類
import { BehaviorSubject } from "rxjs" //分頁配置 export interface PaginationConfig { // 當(dāng)前的頁碼 pageIndex: number; // 總數(shù) total: number; // 當(dāng)前選中的一頁顯示多少個(gè)的數(shù)量 rows: number; // 可選擇的每頁顯示多少個(gè)數(shù)量 rowsOptions?: Array; } //分頁配置初始數(shù)據(jù) export let PaginationInitConfig: PaginationConfig = { pageIndex: 1, total: 0, rows: 10, rowsOptions: [10, 20, 50] } //表格配置 export interface TableConfig extends PaginationConfig { // 是否顯示loading效果 isLoading?: boolean; // 是否處于半選狀態(tài) isCheckIndeterminate?: boolean; // 是否全選狀態(tài) isCheckAll?: boolean; // 是否禁用選中 isCheckDisable?: boolean; //沒有數(shù)據(jù)的提示 noResult?: string; } //表頭 export interface TableHead { titles: string[]; widths?: string[]; //樣式類 src/styles/ 中有公用的表格樣式類 classes?: string[]; sorts?: (boolean | string)[]; } //分頁參數(shù) export interface PageParam { page: number; rows: number; } //排序類型 export type orderType = "desc" | "asc" | null | "" //排序參數(shù) export interface SortParam { orderBy?: string; order?: orderType } // 所有表格的基類 export class BaseTableModel { //表格配置 tableConfig: TableConfig //表格頭部配置 tableHead: TableHead //表格數(shù)據(jù)流 tableData$: BehaviorSubject //排序類型 orderType: orderType //當(dāng)前排序的標(biāo)示 currentSortBy: string constructor( //選中的 key private checkKey: string = "isChecked", //禁用的 key private disabledKey: string = "isDisabled" ) { this.initData() } // 重置數(shù)據(jù) public initData(): void { this.tableHead = { titles: [] } this.tableConfig = { pageIndex: 1, total: 0, rows: 10, rowsOptions: [10, 20, 50], isLoading: false, isCheckIndeterminate: false, isCheckAll: false, isCheckDisable: false, noResult: "暫無數(shù)據(jù)" } this.tableData$ = new BehaviorSubject([]) } /** * 設(shè)置表格配置 * @author GR-05 * @param conf */ setConfig(conf: TableConfig): void { this.tableConfig = Object.assign(this.tableConfig, conf) } /** * 設(shè)置表格頭部標(biāo)題 * @author GR-05 * @param titles */ setHeadTitles(titles: string[]): void { this.tableHead.titles = titles } /** * 設(shè)置表格頭部寬度 * @author GR-05 * @param widths */ setHeadWidths(widths: string[]): void { this.tableHead.widths = widths } /** * 設(shè)置表格頭部樣式類 * @author GR-05 * @param classes */ setHeadClasses(classes: string[]): void { this.tableHead.classes = classes } /** * 設(shè)置表格排序功能 * @author GR-05 * @param sorts */ setHeadSorts(sorts: (boolean | string)[]): void { this.tableHead.sorts = sorts } /** * 設(shè)置當(dāng)前排序類型 * @param ot */ setSortType(ot: orderType) { this.orderType = ot } /** * 設(shè)置當(dāng)前排序標(biāo)識(shí) * @param orderBy */ setSortBy(orderBy: string) { this.currentSortBy = orderBy } /** * 設(shè)置當(dāng)前被點(diǎn)擊的排序標(biāo)示 * @param i 排序數(shù)組索引 */ sortByClick(i: number) { if (this.tableHead.sorts && this.tableHead.sorts[i]) { if (!this.orderType) { this.orderType = "desc" } else { this.orderType == "desc" ? this.orderType = "asc" : this.orderType = "desc" } this.currentSortBy = this.tableHead.sorts[i] as string } } /** * 獲取當(dāng)前的排序參數(shù) */ getCurrentSort(): SortParam { return { order: this.orderType, orderBy: this.currentSortBy } } /** * 設(shè)置表格loading * @author GR-05 * @param flag */ setLoading(flag: boolean = true): void { this.tableConfig.isLoading = flag } /** * 設(shè)置當(dāng)前表格數(shù)據(jù)總數(shù) * @author GR-05 * @param total */ setTotal(total: number): void { this.tableConfig.total = total } setPageAndRows(pageIndex: number, rows: number = 10) { this.tableConfig.pageIndex = pageIndex this.tableConfig.rows = rows } /** * 更新表格數(shù)據(jù)(新數(shù)據(jù)、單選、多選) * @author GR-05 * @param dataList */ setDataList(dataList: T[]): void { this.tableConfig.isCheckAll = false this.tableConfig.isCheckIndeterminate = dataList.filter(item => !item[this.disabledKey]).some(item => item[this.checkKey] == true) this.tableConfig.isCheckAll = dataList.filter(item => !item[this.disabledKey]).every(item => item[this.checkKey] == true) this.tableConfig.isCheckAll ? this.tableConfig.isCheckIndeterminate = false : {} this.tableData$.next(dataList); if (dataList.length == 0) { this.tableConfig.isCheckAll = false } } /** * 獲取已選的項(xiàng) * @author GR-05 */ getCheckItem(): T[] { return this.tableData$.value.filter(item => item[this.checkKey] == true && !item[this.disabledKey]) } }
我們?yōu)槭裁礇]有抽離成組件而是數(shù)據(jù)模型這么一個(gè)類上,主要是因?yàn)?,組件的樣式我們是不確定唯一性的,但數(shù)據(jù)和處理邏輯確是類似的,哪里地方要用到,就在哪個(gè)組件里new一個(gè)就好了;
其中BaseTableModel后面的T可以是所有你想在表格上渲染的任何一個(gè)model類,比如之前的ProductModel,頁面需求需要展示產(chǎn)品的表格列表,則
export class TableModel extends BaseTableModel{ constructor() { super(); } }
那么最后你只需要將BaseTableModel里的tableData$數(shù)據(jù)next成處理好的ProdcuModel數(shù)組就好了。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/98817.html
摘要:新的項(xiàng)目目錄設(shè)計(jì)如下放置靜態(tài)文件業(yè)務(wù)組件入口文件數(shù)據(jù)模型定義數(shù)據(jù)定義工具函數(shù)其中數(shù)據(jù)流實(shí)踐的核心概念就是數(shù)據(jù)模型和數(shù)據(jù)儲(chǔ)存。最后再吃我一發(fā)安利是阿里云業(yè)務(wù)運(yùn)營(yíng)事業(yè)部前端團(tuán)隊(duì)開源的前端構(gòu)建和工程化工具。 本文首發(fā)于阿里云前端dawn團(tuán)隊(duì)專欄。 項(xiàng)目在最初應(yīng)用 MobX 時(shí),對(duì)較為復(fù)雜的多人協(xié)作項(xiàng)目的數(shù)據(jù)流管理方案沒有一個(gè)優(yōu)雅的解決方案,通過對(duì)MobX官方文檔中針對(duì)大型可維護(hù)項(xiàng)目最佳實(shí)踐的...
摘要:它通過數(shù)據(jù)模型進(jìn)行鍵值綁定及事件處理,通過模型集合器提供一套豐富的用于枚舉功能,通過視圖來進(jìn)行事件處理及與現(xiàn)有的通過接口進(jìn)行交互。 本人兼職前端付費(fèi)技術(shù)顧問,如需幫助請(qǐng)加本人微信hawx1993或QQ345823102,非誠(chéng)勿擾 1.為初學(xué)前端而不知道怎么做項(xiàng)目的你指導(dǎo) 2.指導(dǎo)并扎實(shí)你的JavaScript基礎(chǔ) 3.幫你準(zhǔn)備面試并提供相關(guān)指導(dǎo)性意見 4.為你的前端之路提供極具建設(shè)性的...
摘要:前端單元測(cè)試,推薦淘寶開源的工具,簡(jiǎn)單易用,支持眾多測(cè)試框架,也支持調(diào)試。這些也是設(shè)計(jì)前端框架時(shí)需要權(quán)衡的重要方面。最后,其實(shí)大型網(wǎng)站不一定要設(shè)計(jì)自己的前端框架,完全可以選用現(xiàn)有的框架。 有人在知乎上提問如何設(shè)計(jì)大型網(wǎng)站的前端 JavaScript 框架,有不少回答,其中得贊較多的兩個(gè)回答如下: 相對(duì)大型的項(xiàng)目在前端 JS 方面有幾個(gè)需要達(dá)成的目標(biāo): 1. 代碼邏輯分層 ...
閱讀 1167·2023-04-26 03:02
閱讀 1198·2023-04-25 19:18
閱讀 2597·2021-11-23 09:51
閱讀 2580·2021-11-11 16:55
閱讀 2634·2021-10-21 09:39
閱讀 1713·2021-10-09 09:59
閱讀 2008·2021-09-26 09:55
閱讀 3538·2021-09-26 09:55