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