【转发】前端进阶篇之如何编写可维护可升级的代码

 

前言javascript

我还在携程的作业务的时候,每一个看似简单的移动页面背后每每会隐藏5个以上的数据请求,其中最过复杂的当属机票与酒店的订单填写业务代码css

这里先看看比较“简单”的机票代码:html

而后看看稍微复杂的酒店业务逻辑:前端

机票一个页面的代码量达到了5000行代码,而酒店的代码居然超过了8000行,这里还不包括模板(html)文件!!!java

而后初略看了机票的代码,就该页面可能发生的接口请求有19个之多!!!而酒店的的交互DOM事件基本多到了使人发指的地步:ios

固然,机票团队的交互DOM事件已经多到了我笔记本不能截图了:git

就这种体量的页面,若是须要迭代需求、打BUG补丁的话,我敢确定的说,一个BUG的修复很容易引发其它BUG,而上面还仅仅是其中一个业务页面,后面还有强大而复杂的前端框架呢!如此复杂的前端代码维护工做可不是开玩笑的!github

PS:说道此处,不得不为携程的前端水平点个赞,业内少有的单页应用,一套代码H5&Hybrid同时运行不说,还解决了SEO问题,嗯,很赞。web

如何维护这种页面,如何设计这种页面是咱们今天讨论的重点,而上述是携程合并后的代码,他们两个团队的设计思路不便在此处展开。面试

今天,我这里提供一个思路,认真阅读此文可能在如下方面对你有所帮助:

文中是我我的的一些框架&业务开发经验,但愿对各位有用,也但愿各位多多支持讨论,指出文中不足以及提出您的一些建议

因为该项目涉及到了项目拆分与合并,基本属于一个完整的前端工程化案例了,因此将之放到了github上:https://github.com/yexiaochai/mvc

其中工程化一块的代码,后续会由另外一位小伙伴持续更新,若是该文对各位有所帮助的话请各位给项目点个赞、加颗星:)

我相信若是是中级水平的前端,认真阅读此文必定会对你有一点帮助滴。

一个实际的场景

演示地址

http://yexiaochai.github.io/mvc/webapp/bus/list.html

代码仓促,可能会有BUG哦:)

代码地址:https://github.com/yexiaochai/mvc/

页面基本构成

由于订单填写页通常有密度,我这里挑选相对复杂而又没有密度的产品列表页来作说明,其中框架以及业务代码已经作过抽离,不会包含敏感信息,一些优化后续会同步到开源blade框架中去。

咱们这里列表页的首屏页面以下:

简单来讲组成以下:

① 框架级别UI组件UIHeader,头部组件

② 点击日期会出框架级别UI,日历组件UICalendar

③ 点击出发时段、出发汽车站、到达汽车站,皆会出框架级别UI

④ header下面的日期工具栏须要做为独立的业务模块

⑤ 列表区域能够做为独立的业务模块,可是与主业务靠太近,不太适合

⑥ 出发时段、出发汽车站、到达汽车站皆是独立的业务模块

一个页面被咱们拆分红了若干个小模块,咱们只须要关注模块内部的交互实现,而包括业务模块的通讯,业务模块的样式,业务模块的重用,暂时有如下约定:

这里有些朋友可能认为单个模块的CSS以及image也应该参与独立,我这里不太赞成,业务页面样式粒度太细的话会给设计带来不小的麻烦,这里再以通俗的话来讲:尼玛,我CSS功底通常,拆分的太细,对我来讲难度过高……

很差的作法

很差的这个事情实际上是相对的,由于很差的作法通常是比较简单的作法,对于一次性项目或者业务比较简单的页面来讲反而是好的作法,好比这里的业务逻辑能够这样写:

根据以前的经验,若是仅仅包含这些业务逻辑,这样写代码问题不是很是大,代码量预计在800行左右,可是为了完成完整的业务逻辑,咱们这里立刻产生了新的需求。

需求迭代

由于我这里的班次列表,最初是没有URL参数,因此根本没法产出班次列表,页面上全部组件模块都是摆设,因而这里新增一个需求:

因而,咱们这里会新增一个简单的弹出层:

这个看似简单的弹出层,背后却隐藏了一个巨大的陷阱,由于点击出发或者到达时会出城市列表,而城市列表自己就是一个比较复杂的业务:

因而页面的组成发生了改变:

① 自己业务逻辑约800行代码

② 新增出发到达筛选弹出层

③ 出发城市页面,预计300行代码

而弹出层的新增对业务自己形成了深远的影响,原本url是不带有业务参数的,可是点击了弹出层的肯定按钮,须要改变URL参数,而且刷新自己页面的数据,因而简单的一个弹出层新增直接将页面的复杂程度提高了一倍。

因而该页面代码轻轻松松破千了,后续需求迭代js代码量破2000仅仅是时间问题,到时候维护便复杂了,页面复杂无规律的DOM操做将会令你焦头烂额,这个时候组件化开发的优点便得以体现了,因而下面进入组件化开发的设计。

准备工做

整体架构

此次的代码依赖于blade骨架,包括:

① MVC模块,完成经过url获取正确的page控制器,从而经过view.js完成渲染页面的功能

② 数据请求模块,完成接口请求

全站依赖于javascript的继承功能,详情见:【一次面试】再谈javascript中的继承,若是不太了解面向对象编程,文中代码可能会有点吃力,也请各位多多了解。

整体业务架构如图:

框架架构图:

.

下面分别介绍下各个模块,帮助各位在下文中能更好的了解代码,首先是基本MVC的介绍,这里请参考我这篇文章:简单的MVC介绍

全局控制器

其实控制器可谓是变化万千的一个对象,对于服务器端来讲,控制器完成的功能是将本次请求分发到具体的代码模块,由代码模块处理后返回字符串给前端;

对于请求已经来到浏览器的前端来讲,根据此次请求URL(或者其它判断条件),判断该次请求应该由哪一个前端js控制器执行,这是前端控制器干的事情;

当真的此次处理逻辑进入一个具体的page后,这个page事实上也能够做为一个控制器存在……

咱们这里的控制器,主要完成根据当前请求实例化View的功能,而且会提供一些view级别但愿单例使用的接口:

这里属于框架控制器层面的代码,与今天的主题不是很是相关,有兴趣的朋友能够详细读读。

页面基类

这里的核心是页面级别的处理,这里会作比较多的介绍,首先咱们为全部的业务级View提供了一个继承的View:

一个Page级别的View会有如下几个关键属性&方法:

① template,html字符串,不包含请求的基础模块,会构成页面的html骨架层

② events,全部的DOM事件定义处,以事件代理的方式定义,因此没必要担忧执行顺序

③ addEvent,用于页面级别各个阶段的监控事件注册点,通常来讲用户只须要关注不多几个事件,好比:

一个页面的基本写法:

只要按照这种规则写,便能展现页面,而且具有DOM交互事件。

页面模块类

所谓页面模块类,即是用于拆分一个页面为单个组件模块所用类,这里有这些约定:

这里代码能够再优化,但不是咱们这里关注的重点:

数据实体类

这里的数据实体对应着,MVC中的Model,由于以前已经使用model用做了数据请求相关的命名,这里便使用Entity作该工做:

这里的数据实体会以实例的方式注入给模块类实例,他的工做是起一个中枢左右,完成模块之间的通讯,反正很是重要就是了

其它

数据请求统一使用abstract.model,数据前端缓存使用abstract.store,这里由于目标是作页面拆分,请求模块不是关键,各位能够把这段代码看层一个简单的ajax便可:

业务入口

最后简单说下业务入口文件:

很简单的代码,指定了下require的path配置,最后咱们看看入口页面的调用:

接下来,让咱们真实的开始拆分页面吧。

组件式编程

骨架设计

首先,咱们进行最简单的骨架设计,这里依次是其js代码与模板代码:

页面展现如图:

日历工具栏的实现

这里要作的第一步是将日历工具栏模块实现,以数据为先的思考,咱们先实现了一个与日历业务有关的数据实体:

里面完成日期工具栏全部相关数据操做,而且不包含实际的业务逻辑。

而后这里开始设计日期工具栏的模块View:

这个组件模块干了几个事情:

① 首先,dateEntity实体须要由list.js这个主view注入

② 这里为dateEntity注册了两个数据响应事件:

render方法继承至基类,使用template与数据生成html,其中数据产生必须重写父类一个方法:

由于这里的日历数据,默认取当前时间,可是url参数可能传递日期参数,因此定义了一个数据初始化方法:

该方法在主页面渲染结束后会第一时间调用,这个时候日历工具栏便渲染出来,其中日历组件的使用便不予理睬了,主控制器的代码改变以下:

因而,整个界面变成了这个样子:

这里是对应的日历工具模板文件tpl.calendar.html:

搜索工具栏的实现

咱们如今的页面,就算不传任何URL参数,已经能渲染出部分页面了,可是下面出发站汽车等业务数据必须等待班次列表数据请求结束才能替换数据,可是这些数据若是没有出发城市和到达城市是不能发起请求的,因此这里先实现搜索工具栏功能:

在出发城市或者到达城市不存在的话便弹出搜索工具栏,引导用户选择城市,这里新增弹出层须要在主页面控制器(检测主控制器)中使用一个UI组件:

对应搜索弹出层html模板:

这里核心代码是:

因而当URL什么参数都没有的时候,就会弹出这个搜索框

这里也迎来了一个难点,由于城市列表事实上应该是一个独立的可访问的页面,可是这里是想用弹出层的方式调用他,因此我在APP层实现了一个方法能够用弹出层的方式调起一个独立的页面。

这里有一个不一样的地方是,由于咱们点击查询的时候才会作实体数据更新,这里是单纯的作DOM操做了,这里不设置数据实体一个缘由就是:

这个搜索弹出层是一个页面级DOM以外的部分,数据实体变化通常只应该影响Page级别的DOM,除非真的有两个页面级View会公用一个数据实体。

搜索功能完成后,咱们这里即可以进入真正的数据请求功能渲染列表了。

其他模块

在实现数据请求以前,我按照日期模块的方式将下面三个模块的功能也一并完成了,这里惟一不一样的是,这些模块的DOM已经存在,咱们不须要渲染了,完成后的代码大概是这样的:

相关文章
相关标签/搜索