也許跟大家不太一樣,我是這么用TypeScript來寫前端的
點(diǎn)擊上方 前端Q ,關(guān)注公眾號
回復(fù) 加群 ,加入前端Q技術(shù)交流群
一、當(dāng)前一些寫前端的騷操作先羅列一下見到過的一些寫法吧:)
1. interface(或Type)一把梭
掘金上很多文章,一提到 TypeScript,那不得先用 interface 或者 type 來聲明個(gè)數(shù)據(jù)結(jié)構(gòu)嗎?像這樣:
type User = {
nickname: string
avatar?: string
age: number
}
interface User {
nickname: string
avatar?: string
age: number
}
然后其他方法限制下入?yún)㈩愋?,搞定,我掌握?TypeScript 了,工資不得給我漲3000???
這里說明一下, 我司
不允許直接使用interfacetype來定義非裝飾器參數(shù)和配置性參數(shù)之外其他任何數(shù)據(jù)類型。
2. 類型體操整花活
要么把屬性整成只讀了,要么豬狗類型聯(lián)合了,要么豬尾巴搞丟了,要么牛真的會(huì)吹牛逼了。
類型體操確實(shí)玩出了很多花活。昨天說過了:TypeScript最好玩的就是類型體操, 也恰好是最不應(yīng)該出現(xiàn)的東西
3. hook 的無限神話
不知道什么時(shí)候開始,hook 越來越流行。聽說不會(huì)寫 hook 的前端程序員,已經(jīng)算不上高階程序員了, 不 use 點(diǎn)啥都展示不出牛逼的水平。
4. axios 攔截大法好
隨便搜索一下 axios 的文章, 沒有 攔截器 這個(gè)關(guān)鍵詞的文章都算不上 axios 的高端用法了。
昨天的文章有提到一些關(guān)于在前端使用 裝飾器 來實(shí)現(xiàn)一些基于配置的需求實(shí)現(xiàn), 今天其實(shí)想重點(diǎn)聊一聊如何在前端優(yōu)雅的面向?qū)ο蟆?/p>
寫過 Java、SpringBoot、JPA 等代碼的后端程序員應(yīng)該非常熟悉的一些概念:
- 抽象:萬物都可抽象成相關(guān)的類和對象
- 面向?qū)ο螅豪^承、封裝、多態(tài)等特性的面向?qū)ο笤O(shè)計(jì)思維
- 切面:沒有什么是切一刀解決不了的,如果一刀不行, 那就多來幾刀。
- 注解:沒有什么常量是不能使用注解來配置的, 也沒有什么注解是切面想切還能躲得掉的
- 反射:沒有什么是暴力拿取會(huì)失敗的, 即使失敗也沒有異常是丟不出來的
- 實(shí)體:沒有什么是不能抽象到實(shí)體上的, 萬物皆唯一。
- 很多:還有很多,以上描述比較主觀和隨意。
于是我們開始把后端思維往前端來一個(gè)個(gè)的轉(zhuǎn)移:)
1. 抽象和面向?qū)ο?/span>
與后端的交互數(shù)據(jù)對象、 請求的API接口都給抽象到具體的類上去,于是有了:
-
ServiceAPI請求類
abstract class AbstractService{
// 實(shí)現(xiàn)一個(gè)抽象屬性 讓子類們實(shí)現(xiàn)
abstract baseUrl!: string
// 再實(shí)現(xiàn)一些通用的 如增刪改查之類的網(wǎng)絡(luò)請求
// save()
// getDetail()
// deleteById()
// select()
// page()
// disabled()
// ......
}
-
Entity數(shù)據(jù)實(shí)體基類
abstract class AbstractBaseEntity<S extends AbstractService> {
abstract service!: AbstractService
// 任何數(shù)據(jù)都是唯一的 ID
id!: number
// 再來實(shí)現(xiàn)一些數(shù)據(jù)實(shí)體的更新和刪除方法
save(){
await service.save(this.toJson())
Notify.success("新增成功")
}
delete(){
service.deleteById(this.id)
Notify.success("刪除成功")
}
async validate(scene: EntityScene):Promise<void>{
return new Promise((resolve,reject)=>{
// 多場景的話 可以Switch
if(...){
Notify.error("XXX校驗(yàn)失敗")
reject();
}
resove();
})
}
// ......
}
- 子類的實(shí)現(xiàn):)
class UserEntity extends AbstractUserEntity<UserService>{
service = new UserService()
nickname!: string
age!: number
avatar?: string
// 用戶是否成年人
isAdult(): boolean{
return this.age >= 18
}
async validate(scene: EntityScene): Promise<void> {
return new Promise((resove,reject)=>{
if(!this.isAdult()){
Notify.error("用戶未成年, 請確認(rèn)年齡")
reject();
}
await super.validate(scene)
})
}
}
-
View視圖調(diào)用
<template>
<el-input v-model="user.nickname"/>
<el-button @click="onUserSave()">創(chuàng)建用戶</el-button>
</template>
<script setup lang="ts">
const user = ref(new UserEntity())
async function onUserSave(){
await user.validate(EntityScene.SAVE);
await user.save()
}
</script>
2. 裝飾器/切面/反射
裝飾器部分的話,昨天的文章有提到一些了,今天主要所說反射和切面部分。
在 TypeScript 中, 其實(shí)裝飾器本身就可以理解為一個(gè)切面了, 這里與 Java 中還是有很多不同的, 但概念和思維上是基本一致的。
反射 Reflect 是 TypeScript 中比較坑的一個(gè)存在, 目前主要是依賴 reflect-metadata 這個(gè)第三方庫來實(shí)現(xiàn), 將一些元數(shù)據(jù)存儲到 metadata 中, 在需要使用的時(shí)候通過反射的方式來獲取??梢詤⒖歼@篇文章:TypeScript 中的元數(shù)據(jù)以及 reflect-metadata 實(shí)現(xiàn)原理分析[1]
在實(shí)際使用中, 我們早前用的是 class-transformer 這個(gè)庫, 之前我對這個(gè)庫的評價(jià)應(yīng)該是非常高的: “如果沒有 class-transformer 這個(gè)庫, TypeScript 狗都不寫?!?/code>
確實(shí)很棒的一個(gè)庫,但是在后來,我們寫了個(gè)通用的內(nèi)部框架, 為了適配 微信小程序端 以及 uniapp 端, 再加上有一些特殊的業(yè)務(wù)功能以及 class-transfromer 的寫法和命名方式我個(gè)人不太喜歡的種種原因, 我們放棄了這個(gè)庫, 但我們仿照了它的思想重新實(shí)現(xiàn)了一個(gè)內(nèi)部使用的庫,做了一些功能的閹割和新特性的添加。
核心功能的一些說明
- 通過反射進(jìn)行數(shù)據(jù)轉(zhuǎn)換
如將后端API返回的數(shù)據(jù)按照前端的數(shù)據(jù)結(jié)構(gòu)強(qiáng)制進(jìn)行轉(zhuǎn)換, 當(dāng)后端數(shù)據(jù)返回亂七八糟的時(shí)候,保證前端數(shù)據(jù)在使用中不會(huì)出現(xiàn)任何問題, 如下 demo
class UserEntity {
@Type(String) phone!: string;
@Type(RoleEntity) roleInfo!: RoleEntity:
@Type(DeptEntity) @List @Default([]) deptInfoList!: DeptEntity[]
@Type(Boolean) @Default(false) isDisabled!: boolean
}
- 通過反射進(jìn)行配置的存儲和讀取
這個(gè)在昨天的文章中有講到一部分, 比如配置表單、表格、搜索框、權(quán)限 等
3. 再次強(qiáng)調(diào)面向?qū)ο?/span>
為了整個(gè)前端項(xiàng)目的工程化、結(jié)構(gòu)化、高度抽象化,這里不得不再次強(qiáng)調(diào)面向?qū)ο蟮脑O(shè)計(jì):)
- 這是個(gè)拼爹的社會(huì)
一些通用的功能,一旦有復(fù)用的可能, 都可以考慮和嘗試讓其父類進(jìn)行實(shí)現(xiàn), 如需要子類傳入一些特性參數(shù)時(shí), 可以使用抽象方法或抽象屬性(這可是Java中沒有的)來傳入父類實(shí)現(xiàn)過程中需要的特性參數(shù)。
- 合理的抽象分層
將一些特性按照不同的抽象概念進(jìn)行組合與抽離,實(shí)現(xiàn)每個(gè)類的功能都是盡可能不耦合,實(shí)現(xiàn)類的單一職責(zé)。如存在多繼承, 在考慮實(shí)現(xiàn)類的實(shí)現(xiàn)成本前提下,可考慮抽象到接口
interface中。
- 還有很多,有空再一一列舉
4. 嚴(yán)格但又有趣的 tsdoc
我們先來看一些注釋的截圖吧:)
image.png
image.png
image.png
一些詳細(xì)的注釋、棄用的方法、選填的參數(shù)、傳入?yún)?shù)后可能影響或依賴的其他參數(shù),在注釋里寫好玩的 emoji或者圖片,甚至是 直接在注釋里寫調(diào)用 demo, 讓調(diào)用方可以很輕松愉快的對接調(diào)用, 玩歸玩, 確實(shí)對整體項(xiàng)目的質(zhì)量有很大的幫助。
中午跟同事吃飯聊了聊現(xiàn)在國內(nèi)大前端的一個(gè)狀態(tài), 當(dāng)時(shí)聊到一個(gè)關(guān)鍵詞 舒適區(qū), 還有前端整個(gè)技術(shù)棧過于靈活的一些優(yōu)缺點(diǎn), 幾個(gè)大老爺們都發(fā)出了一些感慨, 如果前端能夠更標(biāo)準(zhǔn)化一些, 像 Java 一樣, 說不定前端還能上升幾個(gè)高度。
是的, 我還是那個(gè) Java 仔, 是, 也不僅僅是。
That's all, 今天水的文章到此結(jié)束。
2023-07-26 17:28:28 更新
代碼已經(jīng)在Github開源:github.com/HammCn/AirP…[2]
參考資料
[1]https://juejin.cn/post/7255561917682991163?searchId=202307251249318EA324C7464742541CBF
[2]https://github.com/HammCn/AirPower4T
[3]https://juejin.cn/column/7249148919505682491
關(guān)于本文
https://juejin.cn/post/7259562014417813564
往期推薦
Vue3寫了hook三天,治好了我的組件封裝強(qiáng)迫癥
抖音前端團(tuán)隊(duì)的設(shè)計(jì)稿轉(zhuǎn)代碼 — Semi D2C 實(shí)踐方案
最后
-
歡迎加我微信,拉你進(jìn)技術(shù)群,長期交流學(xué)習(xí)...
-
歡迎關(guān)注「前端Q」,認(rèn)真學(xué)前端,做個(gè)專業(yè)的技術(shù)人...
點(diǎn)個(gè)在看支持我吧
