本文由JDC前端开发部杨小璐撰写
窗外日光弹指过,席间花影坐前移。2019 年以飞一般的速度逝去,2020 年在新冠病毒的扰乱下萍水相逢。病毒是无情的,它限制了人们的脚步,足不出户,可是这并不能阻止咱们思想行进的脚步。2019 年关于担任酷兜项意图前端童鞋来说是异常艰难的一年,需求同时保护 H5 外L 4 ] 2 _ { o a =接版、内嵌微信小程序两套代码。走运的是咱们有相当靠谱的团队,凭仗整! } ] 0 o X l M .个团队(产品、后台、测验)的紧密协作,一路披荆斩棘,完结了 10+ 个版别的迭代和几十个日常优化。
2` r x ) [ e ^019 年酷兜项目经历了多达 13 次的大小版别迭代。为了在 2020 年不断进步酷兜营销能力,完善产品体验,到达市场预期,新版酷兜被寄托着很大% * D x A ; +期望。
什么是酷兜
酷兜是京东为优质大型企业客户专2 2 h Q ` =门打造的“ 0 预算”职工内购福利渠道。酷兜内购商城N : K o # ~ . ) %经过整合包含热销产品、品牌扣头、优选精m ! k v P品、生活服务等优质= Q { 9 @ E Y k H资源,以内部惠购的办法为企业客户的职工供给福礼特权“酷”体验。 究竟“酷j B _ A 5 ? 2 m :”在哪呢?
- 每日更新上万款依据京东商城精选的优质自营产品
- 覆盖 7 大消费场景、时尚穿搭、食物酒水、个人美妆、母婴童装等 29 个京东主营类目
- 支撑依据锦礼渠道小程序或外部系统对接环境下运用,外接版可打通联合登录账号
- 齐备的物流与售后服务,具有全球领先的中小件、大件、冷藏冷冻仓配一体化物流设施
- 支, g Y c撑微信付出,匹配干流在线付出通路
说了这C g s i d a R么多,有没有猎奇酷兜的庐山真面目呢,请看!
好了,广告宣传部分到此结束,接下来咱们将从前端架构、重构功用优化、技能拓宽三方面论述此次重构优化!
前端架构
重构的起因
酷兜在 2020 年要建立齐备的= | U b内购价格体系,整合今日上新、扣头榜等营6 # ; x销模块,优化用户体验等一系列产品目标。
但现实状况是,前端^ z U结构是 2018 年 10 月创立,运用的技能比较老旧。7 # w G k m f在这一年多的时刻里,需求迭代、功用优化a h 8 g ~ ! X @ ]、bug 修正、开发人员的X ^ q B ) F 0 _替换形成代码冗余严峻,逻辑不明晰、开发联调困难。% } ; e (看似简略的需求修正,往往内含玄机,牵一发而动全身。前端技能一p H E日千里,底层结构版别过低,不单单增加了开发人员的兼容性处理,– B | _ [ G 7 $而且无法完成流畅的交互效果,用户体验不佳。
在几回的版别迭代中,产品都会提出疑问,一个小小的需求,为什么需求这么长的开发时刻I m :。说实话,有` U ` ] j _ `些需求、bug 在咱们看来也是b + : 2 ~ +分分钟搞定的事情,能够在酷兜中,就说不准了,一处的改动或许会影响到多处的状况,一个 bug 的处理是以发生多个 bug 为代价的。这简直成为了 bugfest (臭虫集会)。
面临这一系列的问题,也为了更好的满足 2020 年酷兜快速迭代更新的需求以及功用要求,进步用户体验。酷兜团队成员一致认同,前端重构走起!
作为担任酷兜的前端开发人 I j : J员,允许我在这儿小小的 happy 一下,终于能够我的代码我做主了。
前端架构优化
跟着技能的! # & J R 0 $ 8开展,前端承当的事& M – H g @ . X务越来越多,项目也越来越变得像大型工程了,而且越来越杂乱了,需求处理好组员之间的协作,也需求做好事务分块、去耦合来下降保护成本,而且还要保= | 0 [持高功率开发。工程目录结构的优化是能到达这个意图的一种办法。一般来说,不论多页面工% T g A . ( ( S程仍是单页面运用,或z T Y t二者都有,目录结构大致都是以下三种办法:
- 类型分组(文件类型/事务类型等进行分组)
- 模块分块(页面模块/事务模块等进行分块)
- 类型分组与模块分Z P v [ $ x c $块的结合
此次重构,选用的是 Vue2 + N} c f p w j K ?utUI + TypeScript + Gaea 技能栈,在目录结构优化办法上,挑选第二种:模块分块,先来看一下全体目录结构q T q U / : _。
├── .bin # Webpack 装备文件
├── build # 打包文件
├── node_modules # 依靠的模块包(NutUI、postcss-plugin-px2rem)
├= j l W + !── package.json # 项目基本信息
├──1 G % C v ( a ` src #5 9 u s 项意图核心组件
│ ├── asset # 资源文件(co u o Bss、image)
│ ├── component # 公共组件
│ ├── config # 环境装备文件(evn.ts)
│ ├── icons # 存放 svg 格式图标
│ ├── servicE G & % / H o Res # HTTP 恳求装备(HttpClient.ts、GoodsA| h |piService.ts)
│ ├── store # 状况办理(vuex)
│ ├── view # 依据事务场景开发的组件
│ ├── util # 公共办法(util.ts、imgSet.ts、appHelper.ts)k ^ N U 4 = ) 3
│ ├── app.vux $ 5 l k 0 $e # 根组件
│ ├── app.ts # 入口文件
│ ├── router.ts # 页面路由
│ ├── index.html # 主页模板
│ ├── vue-shim-extend.d.ts # 扩展 Vue 大局类型声明
│ └── vue-sh[ | A _ D 2 % vim.d.ts # Typel N aScript 支h 3 ` M a ( ,撑 *.vue 文件装备
├── static # 静态资源(ico图标、vendor.dll` A 5 u.js)
├── READMV 4 4 eE.m_ 4 p T k F V ! `d # 项目描述信息(一些办法运用的留意事项)
└── tsconfig.json # TypeScript 编译设置
有没有感觉这样的工程目录结构很明晰(小小的自恋一下)。0 V p g P q V U其实,工O r i v + – 3 V i程目录结构的设计并没有完结选用模块分g s P块的办法,在 asset 资源文件中,依照文件类9 h | ] U C 8 )型分成了 css、image 文件。尽管对全体目录结构做了调整,可是每个作业空间下的目录结构开发人员能够自由发挥,咱们也约定了创立文件的几点1 K ) 4 % {标准
- 在开发事务场景组件中,依照路由区分,该文件与文件1 D N夹g & h 6称号保持一致
- 在状况办理、恳求装备中,依照事务模块区分
- 就近原则,多处运用的组件放到 component 文件夹中,此路由紧耦合的子组件,放到本文件夹中
- /src 外的文件不应该被引进
这, 9 . (样的目录结构有什么优势呢?
- 工程目录中的文件夹都有明晰的功用,成员之间能很简略快捷的知道某个页面或某个功用块有哪些文件
- 成员之间依照分配的模块开发,避免了代码上的抵触、合并
- 能够依据路由称号,快速定位到文件
好了,有了这样的目录结构,再也不必忧虑开发时找不到文件了。
前端功用优化
此次重构,前端大大小小的优化点咱们大约总结了 14 条:
- 借鉴 NutUI 组件库中 Icon 图标的开发办法,运用 SVG 图标
- 运用 postcss-plugin-px2rem 插件完结 px 与 rem 的转化,完成移动端的自适应( dpr 核算)
- 选用按需加载的办法,进步加载速度
- 产品图片展现运用 NutUI 组件的 Lazyload (图片懒加载),减轻服务器的压力
- 选用 RSA 双向加密办法,确保了加密特N Y )点的安全性
- …
看到这几点有没有调起咱们的胃口,来张大图满足一下咱们!
NutUIj _ B g F d d h U 2.0 组件库
一个聪明的前端工程师,为了能够快速完结版别迭代,除了进步自己的开发功率外,还要懂得会运用东西。挑选一款适宜的组件库g N J % K n a { [,能大大进步前端的开发功率。
此次重B p X构,咱们持续选用 NutUI@ } p 组件库,并将 1.x 版别晋级到 2.x 。抛弃 ! 4 l _ 5 ; q将 NP r . : U & sutUI 组件库放到本地的办法q j L Q,直接运用 NPMp } s O 上的最新包,便利实时更新组件。
NutUI 组件库是一套京东风格的轻量级移动端 Vue 组7 U K y件库。经过 JDRD 前端团队 2 年多的迭代晋级,目前有 50+ 京东移动端项目运用,外部运用项目达 30+ 项目。 GitHub 上得到 1.8k 的 star,NPM 下载量超过 12k。
NutUI 2.x 选用全新架构
与 1.x 版别比较,NutUI 2.x 紧跟年代潮流, G `,依据全新的架O M # 1构开发:
- 依据Webpack4.0开发,具有更快的构建速度,输出更小的 buu ` T t ! ^ ^ N {ndle 文件
- 一次性构建2 U p , Z U 4 _出多种类型的 bundle,兼容各种干流B } H : F 9 5 _ I模块化场景和非模块化引证场景
- 依据Babel7完成了 Polyfill 的智能b X Z 2 /加载,无须额定引进 Babel-poa ? 0lyfill 文件也可兼容低版别阅览器
- 集成Carefree方案,大幅进步开发环境的真机调试功率
- 示例页面 PWA 加持,支撑离线缓存和创立主屏图标
- 接入持续化集成和主动化测验,进步代码牢靠性
- 支3 Q d撑主动生成新组件模板
- …
高功率
现在开源的 UI 组件库琳琅满w M J a + +目,究竟选哪个更适宜呢?咱们先来看一下 MintUI、Vant 、NutUI 版别最终版别` * + ^ r B 4迭代时刻:
团队 | Git# t wHub 最终更新时h & 8 o P [ K Q刻 | 简介 | |
---|---|---|---|
MintUI | 饿了么团队开发、保护 | 2018 年 1 月 16 号 | — |
Vant | 有赞团队开发,保护 | 2020 年 4 月5 号 | Vant 是一个轻量、牢靠的移动端 Vue 组件库 |
NutUI | 京东团队开发、保护 | 2~ – [020 年 4 月 3 号 | 一套京东风格的轻量级移动端 Vue 组件库 |
注:GitHub 最终更新时刻是在 2020 年 4 月 3 号查看的。
从最终版别迭代时刻来说,NutUI 更近一些。开发组件时m b U 7 e 0 Z,研制人员都是尽或许让组件适用于任何的事务场景,较近的迭代时刻能够阐明该组v = /件在日常保护中,不必忧虑在开发过程中,bug 的^ ^ 8 i J D D ;反应无人理睬,影响开发进度。
在重构过程i t r g o e ` & C中,购D H S } 3 D * x P物车产品左滑删去功用,产品列表运用的是 scroller 组件,左滑删去运用的是 leftsli? & T r t k c ? 7p 组件
在苹果手A T I g机上运用的非常流畅,可是在安卓手机上,左滑产品无法呈现删去按钮。原因是 NutUI 库中的 scroller 组件与 leftslip 组件有兼容问题。咱们将问题及N 5 d i时反应后,组件的开发人员快速响应,及时作出修正。几天之后 NutUI 也发布了新的版别,丝毫没有影响项意图开发进度。
高复用率
挑选一个合适的组件库还有更重要的一点便是组件库的运用率,假如仅仅为了组件库中的一个组件,而# t Z ] / T q引进整个库,就有点儿太耗功用了。在重构开端前,咱们梳理了或许用到的组件。
功用 | MintUI | Vant | NutUI |
---|---|---|---|
上拉加载、下拉改写 | √ | √ | |
Dialog 对话框 | √ | √ | √ |
TV w E )oast 吐司 | √ | √ | √ |
回到顶部 | √ | ||
左滑删去 | √ | √ | |
上传 | √ | √ | |
Popup 弹出层 | √ | √ | √ |
Stepper 步进器 | √ | √ | |
图片懒加载 | √ | √ | |
时刻轴 | √ | √ | |
查找栏 | √ | √ | √ |
产品价格 | √ | ||
徽标 | √ | √ |
支撑 TypeScript
最终挑选 NutUI 组件库最重要的一点是,NutUI 2.x 开端支撑 TypeScript ,能有用削减重构时刻。
想要了解更多关于 NutUI 2.0 的特性,能够戳这儿哟!nutr U / Gui.jd.co# z w v c P jm
Gaea 脚手架主动化晋级
Gaea 脚手架,是咱们团队自主开发的一套 Vue 技能栈构建东西z P @ ; C P = 6,依据 Node.js、We^ # [ j j F 3bpack 模版工程等的 Vue 技能栈的整套处理方案,包含了开发、调试、打包上线完整的作业流程。极大的进步了作业功率。是不是觉得这些话有些空,究竟做了哪些晋级呢?那就来点干货吧!
- 新增 HappyPack。HappyPack 与 thread-loader 结合,完成多线程编译,` } & ~ i K加速编译速度,可是需求留意 thread-loader 不能够和 mini-cs| @ j K K = o bs-extractP n ^ 2 C-plugin 结合运用
- 新增 progress-bar-webpack-plugin 编译进度条。
- 新增 cache-loader。在开发环C Y u # V境编译时,运用模块编译缓存,加速编译速度
- 新增 wR 4 ` B ; 6ebpack-bundle-analyzer。能让开发者明晰的看到项目各模块的大小
- 新增g O ; 8 X o # webpack-build-notifier 。webpack 构建完结,能够像咚咚那样,弹出构建成^ j i 3 U果
- 去掉 uglifyjs-webpack-plugin 。W( Y m ` + Aebpack 版别由 3.x 晋级d H l O C到了 4.x , JS 紧缩,webpack4 中内置,不需求单独引进
- …
更t k –多的特性、优化晋级,咱们在这儿就不赘述了。为了让 Gaeaa W H 8 能更有用的进步开发功率,咱们对 Gaea 进行了小小的改动。
开发、测验、上线往往需求不同的环境,咱们不能运用线上环境进行修正、操作的。现阶段,在开发、测验阶段,前端经过判别恳求地址上是否有 debug 参数,来进行环境的切换。看似小小的行为S } R ] | . . m ),费不了多大的事,可是这~ @ 6 h y H关于开发、测验来说,现已相当繁琐了。不光 “京东锦礼” 小程序为了配合预发环境,在恳求路径上增加 debug 参数,就连前端开发时要时刻留意 debug 参数,后台研y z 0 U X v C /制在生成免密登录串时,也需求留意 debug 的存在。真真的牵一发而动全身。就在U x b B e咱们面临这个问题,没有更好的处理办法时, Gaea 脚手架的更新为咱们供给了思路。
新版别的 Gaea ,Webpack 版别有 3.x 晋级到了 4.x ,修正了 Webpack 装备文件,选用多指令 de8 B ^ d h # Y % yv、build、upload 主动化装备恳求 API。那咱们6 c w 2 O为何不依据履行主动化装备的指令,来决议运用环境呢?有了新的思路,那就马上行动起来吧!
/H Z x Y S/ config / evn.ts
switch (( / | n e 4 } 6process.em y x m j * X ` 4nv.NODE_ENV)n / R ^ % N * J P {
case 'development':
case 'upload':
config.bas9 ; / H :eUrl = 'L _ S M d . %https://xxxx-fy.jd.com' // 开发环境 =>z _ ; p i q; npm run dev 或 npm run upload
break
case 'production':
config.baseUrl = 'https://xxxx.jd.com' // 线上环境 => npm run build
break
}
经过上面的装备,在结合 API Service 整合,一致装备 HTTP 恳求环境,完美的处理8 w U z ; X p ?了这一问题,开发、测验再也不必忧虑 debug 参数困扰,或者是搞错线上{ X U环境。
封装 AppHe^ S 7 M m 9 )lper Hybrid 多端交互类,定制 JS API 接入) 4 B + k文档
酷k 4 c ?兜当时项目现已支撑跨渠道多端(微信小程序、第三方 APP 内嵌、H5),作为一个可被外接的 H5 ,原生APP API 是必定少不了的,为了杰出的用户体验 ,咱们需求第三方接入者来严厉依照咱们的 API 标准开发。
首要咱们先考虑一下,为什么要有 API ,举一个常见的场景:d d & # 2 } 5翻开一个新页面,不同端是怎么处理
- 微信小程序 内部开发可控
wx.miniProgram.navigateTo({
url: `/pages/xxx?url=` + encoh d + W ( D 8 )deURIComponent(url)
})
- H5 内部开发可控
window.open(url)
- 原生 APP ios 或者 android | 第三方 APP 内嵌 开发不可控
这个地方就要详细讲解一下,首要咱们的 H5 页面是要被第三方 APP 进行内嵌 WebView 翻开, 首要作为前端的咱们不知道每个第三方 APP 详细语法调用,那么就需求定制一套 JS API 来约定好调用规矩,由前端发x * G & +起,主动调用 JS 发送至 原生8 H D APP 端,大致思路便是,
- 先承认当时K R f g Z z webview 是否支撑 kudou API , 经过查看 navigator.userAgent 来承认,navigator.userAgent 的值原生 APP 能够进行自界说设置
- 区分不同端 android 、ios 别离调用
callApp.postMessage
或webkit.messageHandlers.callApp.postMes{ 8 w H X Xsage
- postMessage 一致发送约定值 Json 字^ J + $ H W j m S符串 { name : ‘ 唯一key,对应不同功用 ‘ , data : ‘ 恣意参数,依据 key 自界说调整’ }
- 客户端各自收到 json 进行 name 键2 ] i H k 0 } 2 W值匹配,做对应逻辑J B . x ; a P处理
知道了基本调用,那么咱们在想一想,多渠道肯定有对应不同的调用办法,那么此处能够简略运用一个工厂模式来处理此逻辑,废话不多说上代码,咱们细品一下
- 创立笼统 APP 类,定制详细功用b ! K W o 0 k 9办法
- 创立完成类(小程序、原生 APP、H5 )
- 创立代工厂类(对外露出详细办法),初始化时,依据当时场景实例化对应类
// 枚举值功用keb d b S L t D @ Ey值
enum Kf k d ! iudouAppSdkType {
NewWebView = "newWebView", /** 翻开新7 f C a n { F Fwebview页面 */
Clo} h A = X B ;seWebView = "closeWebView", /** 封闭当时webview */
Opv o MenLogin = "openLogin", /** 调起登录页 */
SetTitle = "setTitle" /** 设置webview标题 */
}
class NewWebViewParams {
constructor(url: string = "", title: string = "") {
this.url = url;
}
url: stY K 4 r / }ring = "";
title: string) 4 e 4 = "";
}
/** 笼统类 APP 供给详细功% ~ J i 8 H u用 API */
abst` 4 k O # o Qract class App {
abstract newWebView(data: NewWebViewParams): void; /** 翻开新页面 */
abstract closeWebView(): void;; C 3 9 M /** 封闭当时t S Y R d webview 页面 */
abstract openLL w m M % i ogin(): void; /** 翻开登录页 *^ ) O { ` ^ K j F/
abstract setTitle(title: stringt N D l A w a): void; /** 设置 webview 标题 */
}
/** 办法完成类- b z小程序 */
class Miniprogram extends App {
setTitle(title: string): void: } ( + N S f { }// 微信小程序z L ` 主动读取当时 document title
newWebView(data: NewWebViewParams): void {
wx.miniProgram.$ Y F $ g $ I 7navigateTo({
url: `/pages/xxx?url=` + encodeURIComponent(data.url)
})
}
closeWebVie& V }w(): void {0 . - & e T
wx.m i i i f ] 0iniProgram.reLaunch({
url: `/pages/xxx`
});
}
openLogin(): v/ X 4 I m m z %oid {
wx.miniProgram.redirectTo({
url: `/pages/xxx?clear=${true}`
});
}
...
}
/*P C H L r* 办法完成类-原生APP */
class NativeApp extends App {
executed(name: KudouAppSdkType, dat, P ` 3 m 4 C `a: any = {}) {
letF U s 5 1 ! params = { nan 1 ( S n ? B T yme, data }
let str = JSON.stringify(params) // 调用 app 参数输出
const _window: any = window
let _userAgent = navigator.userAgent //app userAgent 输出
if (_userAgent.indexOf('xxx/and1 L Z E l (roid') !== -1) { // 调用 android
try { _window.callApp.postMessage(str) } catch (error) { alert('android error :' + JSON.stringify(error) + 'post android str :' + str) }
} else if (_u| * y ; &serAgent.indexOf('xxx/ios') !== -1) { // 调用 ios
try { _window.webkit.messageHandlers.callApp.po* ~ . 7 * A zstMessage(stC Z 0 _ P Fr) } catch (error) {0 H V a 1 U = ) alert('ios error :' + JSON.stringify(error) + 'post ios str :' + str) }
}
}
setTitle(title: string): void {
this.executed(KudouAppSdkType.Set1 * k l 7 A a gTitle, { title })
}
newWebView(dati @ l F 5a: NewWebVie$ x iwParams): void5 7 b s p + % {
this.executed(KudouAppSdkType.NewWebView, data)
}
closeWebView(): void {
th G T T ] C .is.executed(KudoS m Z ~ S | o m `uAppSdkType.CloseWebViJ w w a x D Bew)
}
open2 ? M K { & & [Login(): void {
this.executed(KudouAppSdkType.OpenLogin)
}
...
}d O D L ?
/**S C U ! ` _ t 办法完成类 - H5 */
class H5 exZ 2 # ; 4 6 X 4 Wtends App {
setTitle(title: string): void {
window.document.title = title;
}
newWebView(data: NewWebViewParams): void {
window.open(data.url)
}
closeWebView(): void {
window.close();
win. b bdow.history.back();
}
openLogin(): void {
// code ...
}
}
/** 代工厂 AppHelper 类 依据不同场景完成对应类 */
export class ApT ! K . i WpHelper {
koudApp: App;
constructor(, O ; M y : # u }) {
if (this.isNativeApp) {
this.koudAR 8 n t Epp = new NativeApp(); // 原生 APP 场景
} else? 6 7 . = : / if (this.isWeChatMiniprogram) { // 小程1 @ 5序场景
this.koudApp = new MiniH P V e Y mprogram();
} else {
this.koudApp = new H5(); // H5 场景
}
}
//o O + Y G 8 z z 查看是否为原生 APP
get i- + # O @sNativeApp() {
return window.navigator.userAgent.indexOf('xxx') !== -1;
}D + - ; 7 R & ;
// 查看是否为I k $ 1 d ! ? # R微信小程序
get isWeChatMiniprogram() {
const ua = navigator.userAgent.toLowerCase().match(/MicroMessenger/gi)
return window.__wxjs_envif h W ] $ L 4ronment === 'miniprogram' || ua &aH ? . 1 C k ` xmp;& ua[0] === 'micromessenger';
}
newWebViewPage(params: NewWebViewParams) {
if (6 2 k 4 [ yparams.url) {
this.koudApp.newWebView(pak O W Frams);
}
}
// 设置标题
setTitle(title: string) {
this.koudApp.setTitle(title);
}
// 令牌失效T # V i,调用登录
loginout() {
this.k] P ` Q 1 V @ Bo[ z H K C [udApp.openLogin()
}
}
export default {
install: functz 0 0 j 3 v `ion (vm) {
vm.prototype.$appHelper = new AppHelper()
},
AppHelper: new AppHelj o ~ [ & p Uper()
}
恳求接口 API Service 模块化
项目中,后端是经过判别恳求头中携带的 cookie 值是否正确,来回来恳求信息的。那也便是说N . u l r s 6 #,每一次数据恳求,都需求在恳求头上增加 cookie 字段,项目中的接口恳求有 50 多个,假如每个都增加,r m 3 O Y F 1或者是未来的某一天,后端要前端X y 0 n N j配合修正恳求头。天啊!这简直是场噩梦,没有技能含F – m 0 – = V a量不说,还简h J / z F J [略犯错。
为了处理这一问题,咱们G . + ; 3 [ – .将 HTTP 恳求一致装备,生成F H ] X HttpClient Class 类,对外露出 post 、 gc a N : 7 l ?et 办法。并对后台回来的过错数据进行一致处理,重新界说回来状况码,避免后端状况码多样性,即使后端状况码做了修正,也不影响前端代码的正确运转。
开发人员都是“偷懒”的,能用一行代. – = | * –码处理的问题,坚决不必两行。
关于 HTTP 恳求咱们仍是不满足,在组件中咱们调用 HttpClient Class 类进行数据恳求时,咱们仍然要回到恳求{ e v a T 5 W m &接口的模块文件,查看入参,或者是 k Q Z O n查看 swagger 文档,怎么才干一望而知呢?* : b灵光一现,决议选用 Class Paraa y ,ms 目标办法束缚入参,从编译办法上进行束缚t E F j `。咱们以增加C 4 #购物车恳求为例:
// 购物车事务模块 API Service 类
class CartApiService {
a$ J C a 6 $ kddCart(params: AddCartParams) {
return this.httpClient.post('/api/xxx', params);
}
}
// AddCartParams
class Af 9 - EddCartP( L M - ) | 9arams {
num: number = 1; /** 参加产品数量:默许值为1 */
skuId: string = "" /** 主站产品ID */
}
在 addCart 办法中,咱们经过B | A W M ] TypeScript 的办法,束缚了 ‘/api/xxx’ 接口的参数是一个目标。N } ] $ B %这样的办法为什么会在调用的时分直接看k . 4到参数信息呢?这就需求 VScode 编辑器的配合了。经过模块的办法引进 AddCartParams 类,将e Y = L . z V鼠标悬停在类上,就能够看到啦!
是不是很4 ! F便利,不光避免了参数类型的不一致,呈现 bug ,也节省了查找办法的时刻,进步开发功率!
注:在 VS code 的编辑器中,当鼠标移动到某些文本之后,稍作片刻就会呈现一个悬停提示窗口,这个窗口里会显示跟鼠标下文本相关的信息。假如想要查看目标就详细信息,需求按下 Cmd 键( Windows 上是 Ctrl )。
JDUntify 埋点模块
咱们运用京东自主研制的子午线增加 PV/UV 埋点,k z –便于查看产品的流量数据与转化率。在供给的埋点列表中,咱们发现v W X u z s –每个点击事情,都有唯一的事情 ID。举个列子:
主页的分类 tab 有8个,依据产品供给的埋点列表,切换不同的分类页签时,需求上报不同的事情 ID 。是不是觉得有不合理– z G _ % L Q d之处,怎么增加了分类页签,那前端就需求对这个新的标签增加埋点事情 ID 。为什么不能经过传递分类页签的称号或 ID 进行上报呢?经过一番研究,发现子午线暂时不支撑O 2 h S 9 G (这个想法,那咱们改怎么优化呢?
咱们将一切的埋点事情 ID 经过枚举类的形式,强束缚埋点事情 ID 值,避免人为修正形成多少字母导致的问题。封装 JDUnify 类,并注册到 Vue 大局变量中,经过 point(‘枚举类型’),进行上报。经过这样的调整,主页切换4 Y / o H L :分类z T b , { W e页签上报@ { e l的代码就简答、清新多了,v _ 7下面代码示例演示给咱们看一下。
JDUnify 封装目标示例
declare var MPingi @ Y N l $ ~ - a: any
export enum PointType {
event_MyInfo = "event_xxx_MyInfo", /** 我的 */
...
eventX - ?_c a ^ / A 2 U RBacktu = - j A d v | &oTop = "event_xxx7 @ s 9 t I z_BacktoTop" /** 回来顶部 */
}
eS B | 2 txport cM b $ x j _ ulass JDUnify {
// jd 子午线埋点
point(eventId: PointType | striM ^ S %ng, enventInfo?: any) {
try {
let click = new MPing.inputs.Click(eventId);
cl$ # Dick = Object.assign(click, enventInfo)
click.updateEventSeries();
new MPing().send(click);
} catch (e) { }
}
}
export default {
install: function (vm) {
vm.proto~ o s M K Y Z 9 Utype.$JDUnify = new JDUnify()
},
JDUnify: n? Y 6 n E 7 hew JDUnify()
}
项目运用示例
按需设置 枚举特点
// 点击"我的"页面埋点
this.$JDUnify.point(PointType.event_MyInfo);
....
// 点击"回来顶部"埋点
this.$JDUnify0 g x ? 4.point(PointType.event_BacktoTop);
只要切换分类标签时,字符串拼接就能够了,就算今后再增加分类页签,只要把事情 ID 放到同一的文件中就行。不只优化了代码的逻辑、还为之后需求的X S g : E改变提早做了准备。优秀 ~~
TypeScript
从 2018 年 Vue 开端重写到 Vue3.0 源码的发布,前几天 beta 版别的发布,前端的同学一向在期待着 Vue3.0 的问世,我相信这个时刻应该不会很M b D : O长了。为了能让酷兜兼容之后的 Vue 3.0,也为了简化了开发的办法、进步代码的可读性与可保护性,咱们在项目重构K P C &时增加了 TyS & H x / p DpeScri s ! M a X fpt。1 o `
咱们来看一下 2016 年 – 2018 年 ES6 与 Typ2 M keScript 的调查表:
ESr y 3 ] ^ ; y6 不同年份调查成果
TyN # 1 peScript 不同年份调查成果
咱们不难发现,ES6 的开展很陡峭,TypeScript 的运用人数尽管没有 ES6 多,但开展趋势很明c d I 0 U x * 0 H显,一向处于上升趋势。2016 年到 2018 年两年的时刻,TypeScript 愿意再次运用的用户份额从 20.8% 增加到了 46.7%。从 2019-01-01 到 2020-04-19 TypeScript 的下载量现已3 a超过了 Vue 的下载量。
运用 TypeSW 3 ? }cript 开发是势( R 4 ? I |在必行的。那运用 TypeScript 能为重构做出哪些奉献呢!
1、削减D T 6 5 8 d @ ] k了注释i Z h B q
在实践开发中,阅览、运用其他$ U S . 4 D E $ y开发者代码的状况是避免不了的,想要快F H j ( 5 * U速掌握代码逻辑、运用规矩,明晰的代码注释就显得尤为重要了。在上文的 “恳求接口 API Service 模块化”i U 0 C } # 这个小节,细心的同学或许就留意到这个了。
结合 VScode 编译东西, : a $ 2 k 4 s咱们能够很清楚的了解:basePax [ | O a `rams 目标的 key 值有哪些,每个 key 的类型是什么,初s 0 –始值是什么,关于程序猿来说是不是比任何注释都清楚明白。也节省了在N P + 5多人开发的过程中,咱们沟通、查找目标界说的时刻。
2、削减 bug
当界说好一个函数时,尽管在注释上明晰标示了参数的类型,可是多人开发过程中,许多人都不会去留意参数的类型。在 JavaScript 中不同的类型有不同的办法,比如:一串数字的长度,string 类型能够运用 length() 办法,但 number 类型就没有 length() 办法。若界说的办法中对参数运用的 le. . N U f E y 7ngth() 办法,传入的是 number 类型,bug 就诞生了。
不知道咱们有& v n H y (没有留意过 toFixeY ? 2 ! *d() 这个办法,将 number 类型的数字依照l T 1 I . x H U银行家舍入规矩保留小数位数,重点来了,得h x ` G C {到的值是 string 类型。一不留意这样的类型转化,就会呈现 bug,但在 TS 中由于界说了字段的类型,若字段赋值了其他的类型,就会呈现过错提示,提早干掉这个 bug。
不光在编写时有过错提示,在 TS 编译时,也会报错提示,尽管 TS 的编译过错并不影响项意图运转| t 2 y ? ` s,但为确保线上不呈现 bug,仍是需求细心看看滴!
TypeScript 与 Vue 的结合,由于需求为变量、办法、类增加类型,这A 0 – 0 % C j关于刚刚接触 Ty+ v ? 6 8 4 y ` HpeScript b ? 的前端同学来说,无疑是增加了开发时刻。从长远考虑, TypeScript 不只便利了成员之间的沟通协作,避免因Z e . M # W r ? l类型转化发生的 bug ,还增强了整个工程的健壮性,为之后的版别迭代打基础。所以我觉得 TypeScript 开发有利有弊,需求– _ # 3 O依据实践事务状况来决议是否运用,R y &但跟着技能的开展,我主张仍是2 U Q V 4 & 7运用 TypeScript 进行工程开发。
技能拓宽
TypeS% d wcript 与e r ] n e 2 Vue 结合,咱们也是第一次运用,为了方面之后的开发人员能够避免这些问题,咱们准备了一些技能干货。怎么你是前端开发工程师,) D y P u + . V对这一章节9 9 R – r ~ ~ 8你没准很感兴趣。打起精神,咱们持续 ~~
尽管 TypeScript 和 ES6 的语法有z Q J 8 B很高的相似度,其实这完全是两种不同的5 I k _ E K 1 % S东西。ES6 仅仅 JavaScript 的言语标准,而 TypeScript 是 Microsoft 开发和保护的一种面向目标的编程言语,与 JavaScript 是两种脚本言语。只不过 T[ 7 N j c G 6ypeScript 中能够运用 JavaScript 中一切的代码和编码概念。可是不管是用 ESg 5 =6 语法进行开发,仍是运用 TypeScript 进行开发,最终都是需求转化成阅览器辨认的 JavaScript 言语。
1、类型查看
JavaScript 是^ a _ & @ E x W弱类型言语,Q n ) t而弱类型言语特点之一便是没有严厉的类型P X 0 . ;界说,界说变量时都是用一致的 var 或 const 要害字,这样尽管不会影响代码的运转,可是运转时隐含的类型转e k @ s K ,化,也会损耗功用。而 TypeScript 则增x L K : ! w ~ Y D加了类7 O i z p = E X型和接口等概念来界说变量的b : ` e ? P 5类型,避免阅览器运转时类型转化。
2、编译过程
JavaScript 从另一个角度分类,也能够成为解说型言语,与编译型言语相对。无需编译,t # y 4只要嵌入{ x s | Y O 1 HT/ T aML 代码中,就能由阅览器加载解说履行。而 TypeScript,在嵌入 HTML 代码之前,经过编译进行类型注解对静态类型的查看,确保变量类型一致,并转化成 Javag q J o @Script 代码,确保阅览器加载解说履行。
TypeScript 在 Vue 中的运用
细心的同学在阅览目录结构– – D G优化那一小节的时分,或许留意到了,比起常见的 Vue 工程目录文件,此工程目录多了 tsconfig.jsonw . c [ 3 b 、vue-shim.d.ts 、vue6 ` ! v-shim-exteq ] h &nd.d.ts 三个文件。
1、tsconfig.jO q u Y Hson
这个文件指定了用来编译这个项意图根文件和编译规矩,与 .babelrc 文件的功用类似。在这个文件中能够设置哪些文件需求 TypeScript 编h ( ~ ^ 9译( include ),哪些文件不需求( exclude )、是否启用装修等。
2、vue-shiY P Cm.d.ts
由于6 R 6 T k 9 W D 4 TypeScript 默许并不支撑 *.vue 后缀的文件,所以在 Vue 项目F ; c Q j中引进的时分需W 4 # ( c F p 4 s求创立x 6 L一个 vue-shim.d.ts 文件,放在项目根目录下,运用 TypeScript declare 声明一个模块,告诉 TypeK { p : $ =Script 需求处理 *.vue 文件。
declare module "*.vue" {
import Vue from "vue";
export default Vue;
}
这儿需求敲一下黑板,在 TypeScript +4 n z j u w 2 s Vue 结构中,代码中导入 *.vue 文件的时分,需求写上 .vue8 K ? 后缀。由于 TypeScript 默许只辨认 *.ts 文件,不辨认 *.vue 文件。
3、vue-shim-extend.d.ts
用于补充在 Vue 中界说的大局变量的接口。在 node_modules 中的 vue/types/vue.d.ts 文件中声明晰 Vue 中的接口,在~ Y G . %开发过程中,经过 ***.t W ] L ]install(vue) 挂载到 Vue.pf x # P jrototype 上的大局变量,在 vue-shim-extend.d.ts 中界说接口,才干在组件中运用。
// 在组件中能够直接运用 this.$toast
declarw : Y v Pe module 'vue/types/vue' {
interface Vue {
$toast: any
}
}
Vue 与 TypeS| p N z s u v Zcript 结合除了目录结构的改变之外,写法上也有很大的G N } = P不同:
说白了,Vue 与 Type* C v d , 4Script 的结合,其实便是选用 TypeScript 中的装修器对 Vue 的事情、办法进行了封装,让 Vue 组件语法在结合了 TypeScr| / y h / R H R Pipt 语法之后更加扁平化,简略了解。经过上面的比照,我就能够看出,尽管写法上做了调整,可是仍然有 Vue 的? V : W ~ o ~ s影子。只要是了装修器的作业原理,Vue 结合 TypeScript 开发就不成问题了。
装修, O N ? # 0 .器
装修器是 ES2016 str J oage-2 的一个草案,可是在 babel 的支撑下,已被广泛运用。毕竟是草案,便是说还没有正式发布,TypeSc2 Z Yript 官网中装修器尽{ c d /管有明晰的文档阐明,可是也是一项实验性特性。 一下关于装修器的知识点、结论是以 TypeScript 装修器为基础哟!
官网给的界说:装修器是一种特别类型的声明,它能够被附加到类声明,办法, 拜访符,特点或参数上。 装修器运用 @expression 这种形式,expression 求值后必须为一个函数,x 3 4 { _ ] #它会c l + – o在运转时被调用,被装修的声明信息做为参数传入。需求留意的两点:
- 装修器只能用于类和D , o 3 d类的办法,不c T p N _ I b U能用于函数,由于存在函数进步
- 装修器对类的行为的改变,是代码编译时发生的,而不是在运转时。也便2 O B是说,装修器实质便是编译时履行的函数。
个人了解:装修器便是E h V :在代码外层包了一层处理逻辑,去掉装修器,代码仍然能够正常运转。就比如:咱们在水龙头外面的起泡器,安装今后,起泡器会在水_ e q c g % – ( F里增加许多: ` x的泡泡,但说白了起泡器对水龙头是否正常作业一点儿影响都没有,卸掉了起泡器,水龙头照样作业。这儿的起泡器就能够当作装修器。
// person.ts
class Person {
name: string;
age: number;
constructor() {
this.namP P 4 6e = 'yugo';
this.age = 12;
console.log('年纪:' + this.age);
}
}
const P = new Person() // 输出 ‘hello Girl!’
咱们界说了 Person 这个类,正常输出了 ‘hello Girl!’。现在咱们咱们为 Person 类增加一个装修器 addAge 。
// addAge 装修器工厂
function addAgeM 0 E 9(args: number) {
return function (Z @ - p $ 3 I L 8target: Function) { //真实的~ j h - 5 f装修器
target.prototype.age = args;
};
}
// person.ts
@addAge(10)
class Person {
name: string;
age: number;
conW & * .structor() {
this.name = 'yugo';
this.age = 12L e o 7 g F X : Y
console.log('年纪:'+this.ag8 # V X y He)
}
}
const P = new Person() // 输出 年纪:12
@addAge 装修器的作用是为类的 age 特点赋值。运转 pS – {erson.ts 输出的成果确不是咱们期待的成果,为什么 age 的值不是 10 ?原因便是上面说到的,装修器D % 5 I w O *实质是编译时履行的函数。运转 person.ts 并实例化 Person 目标,属于运转阶段,所以输出的是 1m W r C 8 [ )2 ,那咱们怎样输出 10 呢?
// person.ts
@addAge(10)
class Person {
name: string;
age: number;
constructor() {
this.name = 'yugo';
// this.age = 12
console.log('年纪:'+this.age)u g i a ) c 6 f !
}
}
const P1 = new Person() // 输出 年纪:10
将 constructor 中对 age 的赋值注释,假如不运用装修器,应该输出 “ 年9 + p A 4 |纪: undefined ”v 9 3 q,但k F 4 z 5现在输出* y W $的是 “ 年纪:10 ”。这也就验证了:装修器是履行在编译阶段的函数,装修器仅仅在代码外层增加了一层逻辑,去掉装修器,代+ H ] l p M 3 u码仍然能够正常运转。
装修器源码解析
Vue 官方引荐的是 vue-class-component 装修器,TypeScript 官网给出的 Vue demo 运用的是 vue-property-decorat i Ztor 装修器,尽管 vue-property-decoro ! + hator 装修器是对 vue-class-component 装修器的扩展,可是这两个插件的运用办法仍是有区别的,在酷兜重构中,运用的是 vue-property-decorator 装修器。
装修器能够分为4类:类装修器、办法装: ? n = d修器、特点装修器、参数装修器。在R Q z这儿就不逐个赘述每种装修器的源码了,以特点装修器 @Prop 为K Y !例,来聊聊装修器是怎么将 Vue 与 TypeScript 结q + T Y H . / G合起来的。: E Y
特点装, 3 R 4 4 0 9 S修器声明在2 z V I ( Y ,一个特点声明之前(紧靠着特点声明)。特点装修器表达式会在运转时作为函数被调用,参数2个参数:
- 关于静态成员来说是类的结构函数,关于实例成员是类F w n的原p m = @ k型目标
- 成员的姓名
别离来看一下:Vue 子组件界说父组件传过来的值的办法
// TypeScript
@Prop({ type: Boolean, default: falsI 6 O C k ) $ v ,e }) rB - _eadonly value:boolean = false
// Vue
pr . 9 ?ropz f 0 G + Z Xs:{ value: { type: Boolean, default: false}}
其实经过 @Prop 装修器,最终取得的便是 Vup & 4 U k R Ge 界说特点办法。咱们无妨在 node_k y ~modules 中找到 vue-property-decorator/v{ j : y * e + ^ Zue-proY & q A X L @ `perty-decorator.js,阅览一下 @Prop 的完成办法。其实最要害的就三个函数:
// vue-class-! X ~component.js 创, D _ K E !立装修器
function createDecorator(factory) {
return function (target, key, index) {
var Ctor = typeof target === 'function' ? target : taC | S _rget.constructor;
if (!Ctor.__decorators__) {
Ctor.__decoratorsS = c a __ = [];
}
if (typeof index !== 'number') {
index = undefined;
}
Ctor.__decorators__.push(function (options) {
rD , P Feturn factory(options, key, index);
});
};
}
// vue-property-component.js 增加类特9 x N T ! -点
function applyMetadata(options, target, key) {
if (!Array.isArray(options) &&am f K Zp; typeof options !== 'function' && typeof o. C K Y : ! 9ptions.type === 'undefine, _ L % d w / B ;d( ! K X K'){
var type =C v I a v Reflect.getMetadata('desm / # W 4 V 6 2 vign:tyv [ & O X i C 5 %pe', target, key);# 0 # F 7 ( q o
if (type !== Object) {
options.type = type;
}
}
}
// vue-property-component.js 界说 prop 装修器
function Prop(options) {
if (options === void 0) { options = {}; }
return fD . t }unction (target, key) {
applyMetadata(options, target, key);
createDecorator(function (componentOptions, k) {
;
(componentOptiom = ) e u K 8ns.props || (componentOptions.props = {}))[k] = options;
})(target, key);
};
}
编译 TypeSc: E ^ E O }ript 文件,履行 @Prop 装修器,便O ( @是在履行 propG ( = 5 g 4 [ K 函数。
1、applyMetadata(options, target, key) 函数
传入 a3 v ^ N D y T L ipplyMetadata() 函数的参数为 { type: Boolean, default: false },if 判别
!+ I n x ^Array.isArray(options) , typeof options !== 'function' , typeof options.type ==E u - A b P S O= 'undefined'
得到 false 。也便是说,调用 applyMetadata() 函数B v # f d s b没有m C ? x得到任何成果。
2、createDecorator(factory) 函数
咱们把调用 createDecorator() ,改造写一下,便利阅览。
function fac = function (componentOptions, k) {
(componentOptio` 9 m $ 8 3 J v 3ns.props || (componentOptions.props = {}))[k] = options;
}
createDecop ~ ) = . z H rrator(fac)(target, key)
是不是明晰许多。经过阅览 createDecorator() 这个函数你会W N A I发现,到最终便是在履[ , d p ? U行 fac 函数} t / Y,参数0 m ! M y O为 tm m X s Earget、keP ; , f k = 4y。那就简略了,只要读懂
(componentOptions.props || (comp 0 h ] `ponentOptions.propE + P M 6 J 6 d ks = {}))[k] = options
这段代码就行了y j Q。 这段代码的意思是:为类特点 props 目标增加 k 值,将其简化便是;
props` # 9 W X h ! D: { k : options }
对用到比如中,传入参数,得到的成果便是:
props: { value : { type: Boolean, default: false} }
是不是与在 Vue 中界说是一模一样的。说了这多这么多,细心的人或许发现了, @Prop 装修器其实便是将 TypeScript 的写法,转化成了 Vue 写法。知道了 @Prop 装修器的完成办法,假如咱们感兴趣能够,能够阅览其他装修器的完成办法,思路与 @Prop 装修器大同小异。
总结
转眼间 2020 年,现已曩昔三分之一。咱们在+ h @ e z l拼尽全力向前跑的时分,别忘了怠慢脚I J ~步,回头看看,为之后的爆发积储能量。此次重构历时 3 个月,尽管“漫长”,可是是值得的。不只更新了自己的技能,也进步了自己对全体架构的掌握。
尽管文_ $ L章马上就要画上句号了,但关于项目重构之路远远没有结束,仍然存在许多的挑战。为了优化用户体验、G K H L 0 o v u P进步开发功率,咱们需求每天以饱满的热情迎接早i D / C上的朝阳Z 8 J { % M,不断的进步自己的技能水平。让咱们一起成长吧,加油!!!