博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
如何写好前端业务代码?
阅读量:6324 次
发布时间:2019-06-22

本文共 5298 字,大约阅读时间需要 17 分钟。

前言

如何写出可维护和可读性高的代码,这一直是一个困扰很多人的问题。关于变量如何起名、如何优化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。

// api.jsexport default {    HOTELLIST: '/hotelList',    HOTELDETAIL: '/hotelDetail'}// Service.jsclass 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里面对应的方法,拿到数据后再调用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)。

对于react来说,最外层的一般称作容器组件,我们会在容器组件里面进行网络请求等副作用的操作。

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

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

wrapper.js

// wrapper.js(伪代码)const Wrapper = (view) => {    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.props            return view({state, actions})        }    }}复制代码

view.js

// view.jsfunction view({    state,    actions}) {        return (        <>            
)}export default Wrapper(view)复制代码

controller.js

// controller.jsclass 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() {            }}复制代码

其他

对于埋点来说,原本也应该放到controller中,但也是可以独立出来一个tracelog层,至于tracelog层如何实现和调用,还是看个人爱好,我比较喜欢用发布订阅的形式。

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

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

// constants.jsexport const cityMapping = {    '1': '北京',    '2': '上海'}export const traceKey = {    'loading': 'PAGE_LOADING'}// tracelog.jsclass TraceLog {    traceLoading = (params) => {        tracelog(traceKey.loading, params);    }}export default new TraceLog// storage.jsexport 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}的键值对格式),这样从时间复杂度上说,比直接遍历两个数组更高效。

总结

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

PS:欢迎大家关注我的公众号【前端小馆】,大家一起来讨论技术。

转载于:https://juejin.im/post/5c92f0f06fb9a070ac66a2be

你可能感兴趣的文章
Linux是如何启动的
查看>>
HiKey连接
查看>>
wget 参数大全
查看>>
使用Loadrunner进行文件的上传和下载
查看>>
Linux C 静态库(.a) 与 动态库(.so) 的详解
查看>>
JS函数
查看>>
sql语句分组/排序/计算总数/连接等sql语句书写
查看>>
【.net 深呼吸】启动一个进程并实时获取状态信息
查看>>
MVC5 的MicrosoftOwinSecurity扩展插件——微信,QQ登录第三方源码
查看>>
分布式系统理论基础 - CAP
查看>>
mysql 用户管理和权限设置
查看>>
【项目管理和构建】十分钟教程,eclipse配置maven + 创建maven项目
查看>>
[转]Asp.Net大型项目实践(11)-基于MVC Action粒度的权限管理【续】【源码在这里】(在线demo,全部源码)...
查看>>
Numpy 的ndarray
查看>>
牛人博客 列表
查看>>
数据库连接池dataesoruce pool深入理解
查看>>
vuejs2.0使用Sortable.js实现的拖拽功能
查看>>
oracle多实例的启动与关闭
查看>>
码农生涯杂记_4
查看>>
10-排序4 统计工龄
查看>>