前言

如何写出可维护和可读性高的代码,这一直是一个困扰很多人的问题。关于变量如何起名、如何优化 if...else 之类的小技巧,这里就不做介绍了,推荐去看《代码大全2》,千书万书,都不如一本《代码大全2》。

工作以来,我一直在写一些重复且交互复杂的页面,也没有整理过自己的思路,这篇文章是我工作一年半来在项目中总结出来的一些经验。

分层

对于业务代码来说,大部分的前端应用都还是以展示数据为主,无非是从接口拿到数据,进行一系列数据格式化后,显示在页面当中。

首先,应当尽可能的进行分层,传统的 mvc 分层很适用于前端开发,但对于复杂页面来说,随着业务逻辑增加,往往会造成 controller 臃肿的问题。因此,在此之上,可以将 controller 再分成 formatter、service 等等。

下面这是一些分层后简单的目录结构。

    + pages+ hotelList+ components+ Header.jsx+ formatter+ index.js+ share+ constants.js+ utils.js+ view.js+ controller.js+ model.js

Service

统一管理所有请求路径,并且将页面中涉及到的网络请求封装为class。

// Service.js
class Service {fetchHotelList = (params) => {return fetch('/hotelList', params);}fetchHotelDetail = (params) => {return fetch('/hotelList', params);}
}
export default new Service

这样带来的好处就是,很清楚的知道页面中涉及了哪些请求,如果使用了 TypeScript,后续某个请求方法名修改了后,在所有调用的地方也会提示错误,非常方便。

formatter

formatter 层储存一些格式化数据的方法,这些方法接收数据,返回新的数据,不应该再涉及到其他的逻辑,这样有利于单元测试。单个 format 函数也不应该格式化过多数据,函数应该根据功能进行适当拆分,合理复用。

mvc

顾名思义,controller 就是 mvc 中的 c,controller 应该是处理各种副作用操作(网络请求、缓存、事件响应等等)的地方,这里的 controller 对应传统服务端的 service。

当处理一个请求的时候,controller 会调用 service 里面对应的方法,拿到数据后再调用formatter 的方法,将格式化后的数据存入 store 中,展示到页面上。

class Controller {fetchHotelList = () => async (dispatch) => {const params = {}this.showLoading();try {const res = await Service.fetchHotelList(params)const hotelList = formatHotelList(res.Data && res.Data.HotelList)dispatch({type: 'UPDATE_HOTELLIST',hotelList})} catch (err) {this.showError(err);} finally {this.hideLoading();}}
}

view 则是指 react 组件,建议尽量用纯函数组件,有了 hooks 之后,react 也会变得更加纯粹(实际上有状态组件也可以看做一个 mvc 的结构,state 是 model,render 是 view,各种handler 方法是 controller)。

在这里,容器组件里面的一些逻辑也可以剥离出来放到 controller中(react-imvc就是这种做法),这样就可以给 controller 赋予生命周期,容器组件只用于纯展示。

在这里,容器组件里面的一些逻辑也可以剥离出来放到 controller 中(react-imvc 就是这种做法),这样就可以给 controller 赋予生命周期,容器组件只用于纯展示。

我们将容器组件的生命周期放到 wrapper 这个高阶组件中,并在里面调用 controller 里面封装的生命周期,这样我们可以就编写更加纯粹的 view,例如:

wrapper.js

// wrapper.js(伪代码)
const Wrapper = (components) => {return class extends Component {constructor(props) {super(props)}componentWillMount() {this.props.pageWillMount && this.props.pageWillMount()}componentDidMount() {this.props.pageDidMount && this.props.pageDidMount()}}componentWillUnmount() {this.props.pageWillLeave && this.props.pageWillLeave()}render() {const {store: state,actions} = this.propsreturn view({state, actions})}}
}

view.js

// view.js
function view({state,actions
}) {return (<><Header title={state.title} handleBack={actions.goBackPage}/><Body /><Footer /></>)
}
export default Wrapper(view)

controller.js

// controller.js
class Controller {pageDidMount() {this.bindScrollEvent('on')console.log('page did  mount')}pageWillLeave() {this.bindScrollEvent('off')console.log('page will leave')}bindScrollEvent(status) {if (status === 'on) {this.bindScrollEvent('off');window.addEventListener('scroll', this.handleScroll);} else if (status === 'off') {window.removeEventListener('scroll', this.handleScroll);}}// 滚动事件handleScroll() {}
}

因此,在这里我们完全将 view 和 model 挂载到 controller 上面,每个页面就以 controller 为入口。

也有一些传统 mvc 框架是以 view 为入口的,将 controller、model 等文件配置信息放到自定义。

<script type="template">
{controller: PATH + 'home.controller.js',model: PATH + 'home.model.js'
}
</script>

如果对路由进行一些处理,我们还可以获得更多生命周期钩子,比如 pageDidBack 等等。

其他

对于埋点来说,原本也应该放到 controller 中,但我更喜欢将埋点操作统一处理,我比较喜欢用发布订阅的形式。

如果还涉及到缓存,那我们也可以再分出来一个 storage 层,这里存放对缓存进行增删查改的各种操作。

对于一些常用的固定不变的值,也可以放到 constants.js,通过引入 constants 来获取值,这样便于后续维护。

// constants.js
export const cityMapping = {'1': '北京','2': '上海'
}
export const traceKey = {'loading': 'PAGE_LOADING'
}
// tracelog.js
class TraceLog {traceLoading = (params) => {tracelog(traceKey.loading, params);}
}
export default new TraceLog// storage.js
export default class Storage {static get instance() {// }setName(name) {//}getName() {//}
}

数据与交互

不过也不代表着这样写就够了,分层只能够保证代码结构上的清晰,真正想写出好的业务代码,最重要的还是你对业务逻辑足够清晰,页面上的数据流动是怎样的?数据结构怎么设计更加合理?页面上有哪些交互?这些交互会带来哪些影响?

以如下酒店列表页为例,这个页面看似简单,实际上包含了很多复杂的交互。

上方的是四个筛选项菜单,点开后里面包含了很多子类筛选项,比如筛选里面包括了双床、大床、三床,价格/星级里面包含了高档/豪华、¥150-¥300等等。

下方是快捷筛选项,对应了部分筛选项菜单里面的子类筛选项。

当我们选中筛选里面的双床后,下方的双床也会被默认选中,反之当我们选中下方的双床后,筛选类别里面的双床也会被选中,名称还会回显到原来的筛选上。

除此之外,我们点击搜索框后,输入'双床',联想词会出现双床,并表示这是个筛选项,如果用户选中了这个双床,我们依然需要筛选项和快捷筛选项默认选中。

这三个地方都涉及到了筛选项,并且修改一个,其他两个地方就要跟着改变,更何况三者的数据来自于三个不同的接口数据,这是多么蛋疼的一件事情!

我借助这个例子来说明,在开始写页面之前,一定要对页面中的隐藏交互和数据流动很熟悉,也需要去设计更加合理的数据结构。

对于深层次的列表结构,键值对会比数组查询速度更快,通过 key 也会更容易和其他数据进行联动,但是却不能保证顺序,有时候可能就需要牺牲空间来换时间。

// 假设筛选项床型type为1,大床id为1,双床id为2.
const bed = {'1-1': {name: '大床',id: 1,type: 1},'1-2': {name: '双床',id: 2,type: 1}
}
const bedSort = ['1-1', '1-2'] // 保证展示顺序

当我们选中大床的时候,只需要保存 '1-1' 这个 key,再和 store 中快捷筛选项列表里面的 key 进行 mapping(快捷筛选项里面的项也应该格式化为 {'type-id': filterItem} 的键值对格式),这样从时间复杂度上说,比直接遍历两个数组更高效。

总结

在开始写业务之前,理应先想清楚需求和业务逻辑,设计出合理的数据结构,对代码进行好的分层,这样在一定程度上可以写出可维护性更高的代码。

js基础代码大全_关于前端业务代码的一些见解相关推荐

  1. 参与知乎 live — 编写优雅的前端业务代码总结

    知乎 live 原地址:编写优雅的前端业务代码 前言 当我们在写业务代码的时候,我们到底在写什么? 其实是对交互的一些处理.所有的交互都是基于用户或者浏览器的一些行为来触发的,比如渲染页面,在页面on ...

  2. autojs命令代码大全_各个主流品牌手机的命令代码大全,安卓工程模式的指令大全!...

    所谓的「命令代码」其实就是安卓工程模式的指令,它可以通过手机拨号盘输入那些相关隐藏代码,让设备快速进入工程测试模式,从而了解那些不为人知的手机硬件信息. 大家广为知晓的两条命令代码,比如说当你输入「* ...

  3. wordvba编程代码大全_这几本基础编程书籍一定要看

    程序员书库(ID:OpenSourceTop) 编译书单来自:https://simpleprogrammer.com/best-programming-books-2019/ 关于程序员类的技术书籍 ...

  4. java代码大全_各种java技术文章汇总整理

    老哥简介 大家好,我是IT老哥.我大学四年读的数学专业,通过一路的自学走到今天.目前在一家大厂做高级java开发工程师.我会分享我的职场经验.java技术.java自学资料等等.我能通过自学进入大厂, ...

  5. c语言代码大全_从学生到专家,C语言开发必读的8本书

    本号总结了在C语言实际开发中必读的8本书,当然这8本书并不能囊括开发的方方面面,但就作者亲身体验来看,这8本书确实每个C语言开发者都必须要读的书,并且是应该深入精读的书. C程序设计语言 相信大家都学 ...

  6. wordvba编程代码大全_面向对象、设计原则、设计模式、编程规范、重构

    面向对象.设计原则.设计模式.编程规范.重构 面向对象 主流的三个编程风格有:面向对象,面向过程,函数式编程. 面向对象是最主流的风格,面向对象具有丰富的特性(封装,抽象,继承,多态). 面向对象 面 ...

  7. python优雅代码大全_代码这样写更优雅(Python版)

    Python 这门语言最大的优点之一就是语法简洁,好的代码就像伪代码一样,干净.整洁.一目了然.但有时候我们写代码,特别是 Python 初学者,往往还是按照其它语言的思维习惯来写,那样的写法不仅运行 ...

  8. 读书笔记_代码大全_第14章_组织直线型代码_第15章_使用条件语句

    组织直线型代码 + 使用条件语句 希望我的读书笔试能带你翻过18页的书 http://www.cnblogs.com/jerry19880126/ <代码大全>第14章和第15章的内容比较 ...

  9. 为什么spring中的controller跳转出错_你的业务代码中Spring声明式事务处理正确了吗?

    Spring 针对 Java Transaction API (JTA).JDBC.Hibernate 和 Java Persistence API(JPA) 等事务 API,实现了一致的编程模型,而 ...

最新文章

  1. 【c语言】蓝桥杯算法提高 输出三个整数的最大数
  2. 调度器Quartz的简述与使用总结
  3. 230u拆解 网卡接口_全网首发 HP ProDesk 400 G5 Desktop Mini 九代1L小主机拆解评测
  4. 与众不同 windows phone (5) - Chooser(选择器)
  5. sketch钢笔工具_设计工具(Sketch,Adobe XD,Figma和InVision Studio)中奇怪的一项功能
  6. .NET Core中间件与依赖注入的一些思考
  7. Dataphin的代码自动化能力如何助力商业决策
  8. 用awk 取出ifconfig eth0中IP的方法
  9. 智能手机RAM和ROM的区别以及SD卡的作用
  10. 单纯形法只有两个约束条件_教学 | 线性规划 7 :单纯形法的引入
  11. 在Azure ML中使用Logistic回归预测客户的贷款资格
  12. java8之Stream API(提取子流和组合流)
  13. python 每天定时运行程序(傻瓜式倒计时)
  14. Matlab 多层(multi-level)小波分析(dwt,dwt2)
  15. 我们为什么来到这个世界上?--黄金雄(杰西)
  16. 推荐系统之 FNN和DeepFM和NFM
  17. Mac BERT 论文解读 Revisiting Pre-trained Models for Chinese Natural Language Processing
  18. 微信小程序开发制作 | 小程序开发者工具功能介绍
  19. 10382 - Watering Grass(贪心 区间覆盖问题)洒水面覆盖
  20. 汇编语言王爽-实验9

热门文章

  1. 前端结构解析系列之二:凯旋门结构
  2. 【Python】any() all() 用法
  3. mug网络用语_各种游戏术语
  4. 检查压缩包是否损坏_工业脉冲布袋除尘器安装前的检查工作
  5. 5.22青海云南同震
  6. 指针的指针(二级指针)本质
  7. Android投屏利器vysor
  8. Scheme学习系列一 :源码安装Gambit
  9. linux shell的sleep指定延时单位
  10. Vue之webpack之基本使用