最美情侣中文字幕电影,在线麻豆精品传媒,在线网站高清黄,久久黄色视频

歡迎光臨散文網(wǎng) 會員登陸 & 注冊

使用裝飾器實(shí)現(xiàn)接口字段映射與Mock

2023-08-21 15:41 作者:午安西瓜  | 我要投稿

前言

實(shí)現(xiàn)了個(gè)能滿足題目要求的小插件,type-json-mapper,對如何實(shí)現(xiàn)不感興趣的小伙伴可以直接跳到 使用 。文中代碼只是示例代碼,只為講清原理,源碼已經(jīng)開源 https://github.com/LuciferHuang/type-json-mapper,歡迎 star ??

背景

一個(gè)前端項(xiàng)目穩(wěn)定運(yùn)行一段時(shí)間以后。

突然有一天,后端同學(xué)找到你,告訴你原先的 Student.name 要改成 Student.fullName,你一遍遍去查代碼,查找 Student.name → 修改 → 自測,確保修改不會有問題。

終于,你成功把 Student.name 都改成了 Student.fullName。

然而,沒過幾天,某個(gè)一直正常的功能突然不能使用了,你開始調(diào)試,發(fā)現(xiàn)原先接口一直返回整數(shù)類型的 age 字段突然變成字符串類型了,你找到后端,后端同學(xué)來了一句 “前端不做檢驗(yàn)嗎?”

卑微前端

下次會改什么字段,下次哪個(gè)字段類型又會出問題,想想都孩怕,難道沒有一勞永逸的辦法能解決這個(gè)問題嗎?

腦門一閃

當(dāng)然有,有點(diǎn) oop 編程語言基礎(chǔ)的,馬上就會想到,這不就是加個(gè) adapter 的事嗎,很多語言都內(nèi)置 adaapter,but,找了一圈發(fā)現(xiàn)沒有能實(shí)現(xiàn)類似功能的插件(有找到的也歡迎評論區(qū)補(bǔ)充一哈)

算了,不找了(懶了),干脆自己造個(gè)輪子

需求

最核心的問題就是要達(dá)到:接口字段的修改不能影響項(xiàng)目中實(shí)際使用的字段,無論是字段名的修改還是類型的修改

這里考慮使用裝飾器附帶額外信息,主要是接口字段信息,與需要轉(zhuǎn)換的類型

既然可以轉(zhuǎn)換類型了,考慮把字段 “翻譯” 功能加上

既然能轉(zhuǎn)換了,能就再加個(gè) Mock 吧,擺脫開發(fā)過程中對后端接口的依賴

設(shè)計(jì)

語言:typescript 構(gòu)建工具:rollup 自動化測試:jest 代碼規(guī)范:eslint + prettier 提交規(guī)范:commitlint

Decorator

首先,我們需要一個(gè)對象

可能大概也許是粉絲老婆

不對,是這個(gè)對象 {}

class?Lesson?{
??public?name:?string;
??public?teacher:?string;
??public?datetime:?string;
??public?applicants:?number;
??public?compulsory:?boolean;
??constructor()?{
????this.name?=?"";
????this.teacher?=?"";
????this.datetime?=?"";
????this.compulsory?=?false;
??}
}

上面的代碼,就是我們構(gòu)造出的 Lesson 類,它的屬性字段就是我們會在項(xiàng)目中實(shí)際使用的字段

現(xiàn)在我們需要把這個(gè)類的屬性字段與接口返回的字段對應(yīng)上,這時(shí)候就需要用到 裝飾器 了,隨便取個(gè)名字,我這里是用 mapperProperty,接收兩個(gè)參數(shù),第一個(gè)是接口返回的字段名,第二個(gè)是期望最終得到的類型(不是接口字段本身的類型)

class?Lesson?{
??@mapperProperty("ClassName",?"string")
??public?name:?string;
??@mapperProperty("TeacherName",?"string")
??public?teacher:?string;
??@mapperProperty("DateTime",?"datetime")
??public?datetime:?string;
??@mapperProperty("ApplicantNumber",?"int")
??public?applicants:?number;
??@mapperProperty("Compulsory",?"boolean")
??public?compulsory:?boolean;
??constructor()?{
????this.name?=?"";
????this.teacher?=?"";
????this.datetime?=?"";
????this.date?=?"";
????this.time?=?"";
????this.compulsory?=?false;
??}
}

如上面的代碼,我們給每個(gè)屬性字段都加上了裝飾器,并告知了接口中對應(yīng)的字段名稱,以及我們希望得到的類型。 例如代碼中的 applicants 字段,對應(yīng)了接口中的 ApplicantNumber 字段,無論接口返回的是字符串還是數(shù)值類型,我們都希望最終得到的是 int 類型(指代整數(shù))的數(shù)據(jù)

接下來要把接口字段名稱與我們期望得到的類型先緩存起來

這里我們借助 Reflect Metadata 實(shí)現(xiàn)緩存

示例代碼如下

function?mapperProperty(apiField,?type)?{
??Reflect.metadata("key",?{
????apiField,?//?接口字段名
????type,?//?期望類型
??});
}

Reflect Metadata 是 ES7 的一個(gè)提案,它主要用來在聲明的時(shí)候添加和讀取元數(shù)據(jù);我們使用 reflect-metadata 來模擬該功能

Transform

有了接口字段名與期望的類型,接下來的轉(zhuǎn)換就簡單了

  1. 第一步,先讀取上一步緩存的元數(shù)據(jù)信息

    const?instance?=?new?Lesson();
    const?meta?=?Reflect.getMetadata("key",?instance,?"applicants");
    console.log(meta);

    這里的 key 即元數(shù)據(jù)的鍵,上面的代碼是讀取 Lesson 類中 applicants 字段的元數(shù)據(jù),meta 打印的結(jié)果如下

    {
    ????apiField:?'ApplicantNumber',
    ????type:?'int'
    }
  2. 第二步,轉(zhuǎn)換

    function?deserialize(clazz,?json)?{
    ??const?instance?=?new?clazz();
    ??const?meta?=?Reflect.getMetadata("key",?instance,?"applicants");
    ??const?{?apiField,?type?}?=?meta;
    ??const?ori?=?json[apiField];?//?json?為接口返回的數(shù)據(jù)
    ??let?value;
    ??switch?(type)?{
    ????case?"int":
    ??????value?=?parseInt(ori,?10);
    ??????break;
    ????//?其它類型轉(zhuǎn)換
    ??}
    ??//?后續(xù)處理
    }

    到這基本就實(shí)現(xiàn)了最核心的能力,只要愿意可以擴(kuò)展更多類型,歡迎一起來完善

Object and Array

對象與數(shù)組的轉(zhuǎn)換與基本類型的轉(zhuǎn)換大差不差,這里我將對象、數(shù)組的裝飾器命名為 deepMapperProperty,只需將第二個(gè)參數(shù)的類型,改為接收一個(gè)類即可

示例代碼如下

function?deepMapperProperty(apiField,?clazz)?{
??Reflect.metadata("key",?{
????apiField,?//?接口字段名
????clazz,?//?子級
??});
}

取值方式同上,不再贅述了,只需改一下轉(zhuǎn)換的代碼

轉(zhuǎn)換對象的示例代碼如下,遞歸調(diào)用一下即可

const?{?clazz?}?=?meta;
if?(clazz)?{
??value?=?deserialize(clazz,?value);
}

數(shù)組則直接使用 map 遍歷

function?deserializeArr(clazz,?list)?{
??return?list.map((ele)?=>?deserialize(clazz,?ele));
}

Mock

模擬數(shù)據(jù)部分,是直接返回的前端項(xiàng)目中使用的字段,而非修改接口字段的返回值

實(shí)現(xiàn)模擬數(shù)據(jù)攏共分三步:

  1. 與轉(zhuǎn)換同樣的步驟,要先讀取字段的期望類型,這里只需要類型即可

    • 遍歷讀取類中各個(gè)字段的元數(shù)據(jù),得到各個(gè)字段的期望類型

  2. 根據(jù)期望類型使用不同的隨機(jī)函數(shù),生成相應(yīng)類型的數(shù)據(jù),這里我封裝了三種類型的隨機(jī)函數(shù)

    • 獲取隨機(jī)整數(shù)

    • 獲取隨機(jī)字符串

    • 獲取隨機(jī)小數(shù)

  3. 針對對象與數(shù)組特殊處理

    • 對象:這個(gè)簡單,老規(guī)矩,遞歸解決

    • 數(shù)組:數(shù)組需要先隨機(jī)生成一下數(shù)組長度,再使用 map 遍歷,遞歸調(diào)用一下 mock 函數(shù)

使用

安裝

npm?i?type-json-mapper

屬性裝飾器

內(nèi)置三種類屬性裝飾器:

@mapperProperty(apiField, type)

基本數(shù)據(jù)類型使用該裝飾器

接收兩個(gè)參數(shù):

  • apiField:接口字段名

  • type:字段轉(zhuǎn)換類型(可選值:string | int | flot | boolean | date | time | datetime)

@deepMapperProperty (apiField, Class)

對象/數(shù)組使用該裝飾器

接收兩個(gè)參數(shù):

  • apiField:接口字段名

  • Class:類

@filterMapperProperty(apiField, filterFunc)

自定義過濾器(翻譯)使用該裝飾器

接收兩個(gè)參數(shù):

  • apiField:接口字段名

  • filterFunc:自定義過濾器函數(shù)

const?filterFunc?=?(value)?=>?{
??return?"translated?text";
};

方法

deserialize(Clazz, json)

反序列化 json 對象

  • Clazz:類

  • json:接口返回的對象數(shù)據(jù)

deserializeArr(Clazz, list)

反序列化數(shù)組

  • Clazz:類

  • list:接口返回的數(shù)組數(shù)據(jù)

mock(Clazz, option)

生成模擬數(shù)據(jù)

  • Clazz:類

  • option:mock 配置

mock 配置

名稱類型描述默認(rèn)值fieldLengthObject字段長度-arrayFieldsstring[]數(shù)組類型字段-

fieldLength

數(shù)據(jù)類型length 含義string字符串長度int最大整數(shù)float字符長度(保留兩位小數(shù))

例:

class?Student?{
??@mapperProperty("StudentID",?"string")
??public?id:?string;
??@mapperProperty("StudentName",?"string")
??public?name:?string;
??@mapperProperty("StudentAge",?"int")
??public?age:?number;
??@mapperProperty("Grade",?"float")
??public?grade:?number;

??constructor()?{
????this.id?=?"";
????this.name?=?"";
????this.age?=?0;
????this.grade?=?0;
??}
}

mock(Student,?{?fieldLength:?{?age:?20,?grade:?4,?name:?6?}?});
/**
?*?age:?20?表示隨機(jī)生成的?age?字段的范圍在?1?~?20?之間
?*?grade:?4?表述隨機(jī)生成的?grade?字段是兩位整數(shù)加兩位小數(shù)的形式,共4個(gè)數(shù)字字符(如:23.33)
?*?name:?6?表述將隨機(jī)生成長度為?6?的隨機(jī)字符串
?*/

使用示例

這里預(yù)先造了幾個(gè)類,并給類屬性加上了裝飾器

import?{
??mapperProperty,
??deepMapperProperty,
??filterMapperProperty,
}?from?"type-json-mapper";

class?Lesson?{
??@mapperProperty("ClassName",?"string")
??public?name:?string;
??@mapperProperty("Teacher",?"string")
??public?teacher:?string;
??@mapperProperty("DateTime",?"datetime")
??public?datetime:?string;
??@mapperProperty("Date",?"date")
??public?date:?string;
??@mapperProperty("Time",?"time")
??public?time:?string;
??@mapperProperty("Compulsory",?"boolean")
??public?compulsory:?boolean;

??constructor()?{
????this.name?=?"";
????this.teacher?=?"";
????this.datetime?=?"";
????this.date?=?"";
????this.time?=?"";
????this.compulsory?=?false;
??}
}

class?Address?{
??@mapperProperty("province",?"string")
??public?province:?string;
??@mapperProperty("city",?"string")
??public?city:?string;
??@mapperProperty("full_address",?"string")
??public?fullAddress:?string;

??constructor()?{
????this.province?=?"";
????this.city?=?"";
????this.fullAddress?=?"";
??}
}

//?狀態(tài)映射關(guān)系
const?stateMap?=?{?"1":?"讀書中",?"2":?"輟學(xué)",?"3":?"畢業(yè)"?};

class?Student?{
??@mapperProperty("StudentID",?"string")
??public?id:?string;
??@mapperProperty("StudentName",?"string")
??public?name:?string;
??@mapperProperty("StudentAge",?"int")
??public?age:?number;
??@mapperProperty("StudentSex",?"string")
??public?sex:?string;
??@mapperProperty("Grade",?"float")
??public?grade:?number;
??@deepMapperProperty("Address",?Address)
??public?address?:?Address;
??@deepMapperProperty("Lessons",?Lesson)
??public?lessons?:?Lesson[];
??@filterMapperProperty("State",?(val:?number)?=>?stateMap[`${val}`])
??public?status:?string;
??@filterMapperProperty("Position",?(val:?number)?=>?stateMap[`${val}`])
??public?position:?string;
??public?extra:?string;

??constructor()?{
????this.id?=?"";
????this.name?=?"";
????this.age?=?0;
????this.sex?=?"";
????this.grade?=?0;
????this.address?=?undefined;
????this.lessons?=?undefined;
????this.status?=?"";
????this.position?=?"";
????this.extra?=?"";
??}
}

以下是接口返回的數(shù)據(jù):

const?json?=?[
??{
????StudentID:?"123456",
????StudentName:?"李子明",
????StudentAge:?"10",
????StudentSex:?1,
????Grade:?"98.6",
????Address:?{
??????province:?"廣東",
??????city:?"深圳",
??????full_address:?"xxx小學(xué)三年二班",
????},
????Lessons:?[
??????{
????????ClassName:?"中國上下五千年",
????????Teacher:?"建國老師",
????????DateTime:?1609430399000,
????????Date:?1609430399000,
????????Time:?1609430399000,
????????Compulsory:?1,
??????},
??????{
????????ClassName:?"古箏的魅力",
????????Teacher:?"美麗老師",
????????DateTime:?"",
??????},
????],
????State:?1,
????Position:?123,
????extra:?"額外信息",
??},
??{
????StudentID:?"888888",
????StudentName:?"丁儀",
????StudentAge:?"18",
????StudentSex:?2,
????Grade:?null,
????Address:?{
??????province:?"浙江",
??????city:?"杭州",
??????full_address:?"xxx中學(xué)高三二班",
????},
????Lessons:?[],
????State:?2,
??},
];

開始轉(zhuǎn)換,因接口返回的是數(shù)組,這里使用 deserializeArr

import?{?deserializeArr?}?from?"type-json-mapper";
try?{
??const?[first,?second]?=?deserializeArr(Student,?json);
??console.log(first);
??console.log(second);
}?catch?(err)?{
??console.error(err);
}

輸出結(jié)果如下

//?first
{
??id:?"123456",
??name:?"李子明",
??age:?10,
??sex:?"1",
??grade:?98.6,
??address:?{?province:?"廣東",?city:?"深圳",?fullAddress:?"xxx小學(xué)三?年二班"?},
??lessons:?[
????{
??????name:?"中國上下五千年",
??????teacher:?"建國老師",
??????datetime:?"2020-12-31?23:59:59",
??????date:?"2020-12-31",
??????time:?"23:59:59",
??????compulsory:?true,
????},
????{
??????name:?"古箏的魅力",
??????teacher:?"美麗老師",
??????datetime:?"",
??????date:?undefined,
??????time:?undefined,
??????compulsory:?undefined,
????},
??],
??status:?"讀書中",
??position:?123,
??extra:?"額外信息",
};
//?second
{
??id:?"888888",
??name:?"丁儀",
??age:?18,
??sex:?"2",
??grade:?null,
??address:?{?province:?"浙江",?city:?"杭州",?fullAddress:?"xxx中學(xué)高三二班"?},
??lessons:?[],
??status:?"輟學(xué)",
??position:?undefined,
??extra:?undefined,
};

如果后端接口還沒開發(fā)完成,我們還可以直接 mock

import?{?mock?}?from?"type-json-mapper";
const?res?=?mock(Student,?{
??fieldLength:?{?age:?20,?grade:?4,?name:?6?},
??arrayFields:?["lessons"],
});
console.log(res);

輸出結(jié)果如下

{
??id:?'QGBLBA',
??name:?'KTFH6d',
??age:?4,
??sex:?'IINfTm',
??grade:?76.15,
??address:?{?province:?'qvbCte',?city:?'DbHfFZ',?fullAddress:?'BQ4uIL'?},
??lessons:?[
????{
??????name:?'JDtNMx',
??????teacher:?'AeI6hB',
??????datetime:?'2023-2-18?15:00:07',
??????date:?'2023-2-18',
??????time:?'15:00:07',
??????compulsory:?true
????},
????{
??????name:?'BIggA8',
??????teacher:?'8byaId',
??????datetime:?'2023-2-18?15:00:07',
??????date:?'2023-2-18',
??????time:?'15:00:07',
??????compulsory:?false
????},
????{
??????name:?'pVda1n',
??????teacher:?'BPCmwa',
??????datetime:?'2023-2-18?15:00:07',
??????date:?'2023-2-18',
??????time:?'15:00:07',
??????compulsory:?false
????}
??],
??status:?'',
??position:?'',
??extra:?''
}

后記

雖然要多維護(hù)一套類,看似麻煩(確實(shí)很麻煩??),但是加強(qiáng)了代碼的健壯性,擺脫對接口的依賴;最重要的是堵住了后端的嘴(bushi)


使用裝飾器實(shí)現(xiàn)接口字段映射與Mock的評論 (共 條)

分享到微博請遵守國家法律
大姚县| 靖江市| 新疆| 麻江县| 京山县| 贺州市| 苍南县| 水城县| 和顺县| 罗平县| 巴东县| 蓝山县| 玉门市| 涞源县| 乳山市| 白水县| 乌鲁木齐县| 措勤县| 黔西| 新闻| 修水县| 靖西县| 吴桥县| 华亭县| 饶河县| 中西区| 建湖县| 延安市| 泰宁县| 芦山县| 客服| 宁国市| 水城县| 永年县| 潼南县| 舟山市| 和田县| 五莲县| 克什克腾旗| 保定市| 邢台县|