在前端架构设计这块也已经工作了一段时间,也翻遍了很多书籍,但是就目前来说笔者还是没有看过真正把前端架构讲好的书,加上现在前端技术的发展诞生了许多新的框架,如:vue、react、angular,这也越来越淡化了前端工程师们对架构设计的积极性,本着按这些技术本身的框架写就好,不用管其他,怎么方便怎么来的思维,最后带来的后果就是代码只有自己看的懂,一处修改处处bug,不利于多人协作,代码逻辑不清等各种常见问题,鉴于这些原因笔者还是想着大胆的提出一些自己的想法,当然也仅限于笔者自己的一些拙见,欢迎大家一起加入前端架构设计的探讨,打造更加美好的前端生态。

vue.js是目前最流行的一套用于构建用户界面的渐进式框架。官网的介绍如下:

Vue (读音 /vjuː/,类似于 view) 是一套用于构建用户界面的渐进式框架。与其它大型框架不同的是,Vue 被设计为可以自底向上逐层应用。Vue 的核心库只关注视图层,不仅易于上手,还便于与第三方库或既有项目整合。

这里明确说了一个问题,vue的核心库只关注视图层,那么我们在写一个前端系统的时候是否只有视图层,我想不是的,接触过三层架构的人可能知道,它应该还有业务逻辑层和数据访问层,也就是说vue并没有为广大前端工程师去考虑如何设计业务逻辑层和数据访问层,这些就是我们在设计前端架构的时候需要考虑的事情了。

为了在页面上显示一个“hello world”,我们可以直接写的html的元素里面,如:

<div>hello world</div>

还可以在html页面中用JavaScript代码来实现,如:

<div id="msg"></div>
<script>document.getElementById('msg').innerHTML="hello world"</script>

还有一个办法是什么呢,我们可以创建一个hello.js,然后在html页面中引入这个hello.js,如:

<!DOCTYPE html>
<html>
	<head>
		<meta charset="UTF-8">
	</head>
	<body>
		<div id="msg"></div>
		<script type="text/javascript" src="hello.js" ></script>
	</body>
</html>

//hello.js
document.getElementById('msg').innerHTML='hello world';

    这应该就是目前比较常见的几种实现方式了,这三种方式基本可以代表系统不同阶段的设计问题了,如果我们的系统还非常非常简单,我们可能会选择第一种,虽然它的灵活性是最低的,但是在系统还只是一个demo的时候,它也是最快的,这一点我相信大家应该是认可的,可是现实开发中,我们的系统肯定不会简单到这种地步,我们需要保证一定的灵活性,所以第三种应该是最常见的方式,至少它做到了将页面与js代码分离,这在开发过程中会更方便我们实现某些需求,这就是一种架构变化的体现。

随着我们系统一直变大,然后人们发现这种方式也不是最好的,虽然解决了一部分问题,但是代码还是会混乱,还是不好维护,然后我们就企图一直优化一直优化,然后话题回到我们要讲的vue,如果用vue来实现上面说的功能,它可以这么来实现:

<div id="app">
  {{ message }}
</div>
new Vue({
  el: '#app',
  data: {
    message: 'hello world'
  }
})

是一种类似模板的方式实现的,但是不同的是vue可以实现双向数据绑定,页面与数据之间的维护由vue来实现,我们不再需要使用getElementById来获取元素,再修改元素,这就大大减轻了我们在html元素和数据之间的维护成本,再加上vue提供的一系列其他操作,我们在开发前端的过程中就像多了一把神器一样。

但是上面的方式并没有解决一个问题,就是当我们的系统比较大的情况下,html元素可能会一层嵌套一层,而且量也非常大,当我们看到那么多的html代码的时候,那将是一件非常头疼的事,而且页面上肯定会有很多冗余的html代码,我们可能更希望的是修改了一处相同,其他相同的地方也能跟着变化,而不是打开系统的每个页面去查找相同的地方,然后修改,这种涉及到的变动太大了,维护成本那是相当的高,所以vue提供了这样一个功能:组件,再来一段官方解释:

组件 (Component) 是 Vue.js 最强大的功能之一。组件可以扩展 HTML 元素,封装可重用的代码。在较高层面上,组件是自定义元素,Vue.js 的编译器为它添加特殊功能。在有些情况下,组件也可以表现为用 is 特性进行了扩展的原生 HTML 元素。

这样就比较方便了,我们可以将很多页面重复的地方进行统一划分到一个组件中,也可以将一个页面中的元素划分到几个组件中,只要你觉得有这个必要。

好了,vue能给我们解决的视图层的问题好像基本也就差不多了,貌似我们已经可以凭借着vue带给我们的便利驰聘前端沙场了。那么问题来了,我们在开发一个项目的时候,是否每一个数据都像hello world这么简单,我想没有人会告诉我真的就这么简单吧,应该说前端的数据绝大部分都是发送请求到后台,然后由后台返回。这中间会涉及到一些问题,前端需要发送请求到后台,后台返回的数据却不一定是前端想要的格式,尤其是现在主推前后端分离模式,这种情况肯定就更常见了,而且有些系统肯定还会遇到这样的情况,数据不是从一个后台接口系统过来的,可能会用上其他的三方系统提供的接口,比如:百度提供的某些API,这个时候更是无法限定后台数据返回格式。当然了,你也可以跟技术负责人说:我就要后台给我返回我要的格式,需要请求三方系统的接口的时候,让后台先请求一遍,转换成我想要的格式再返回给我,这不失为一个办法,但是呢,我想说的是,如果我是你们公司的技术负责人,不好意思,这个要求我没办法满足你,我们来算一笔账:

假设公司现有后台系统能处理的QPS为500(请求数/秒),即平均每个请求耗时为:
1000/500=2ms,
现在需要将一个前端可以处理的任务放到后台系统进行处理,假设处理这个任务不管前端还是后台都是耗时2ms,
那么理想状态下后台系统的QPS将会变成:
1000/4=250(请求数/秒)
这个结果直接导致后台系统处理数据的能力下降一半,如果后台系统必须要满足QPS为500,
那么后台系统就需要在现有基础上至少增加一倍的配置来应对该变化,后台系统的维护成本也将相应的变高,
而如果把这个任务放在前端处理,前端请求耗时将会是在原有耗时基础上增加2ms左右,不会出现叠加效果,对整个系统的影响可以忽略不计。

看了这个数据之后,相信我们应该可以统一一个观点,那就是前端肯定不会简单到只写页面,肯定会涉及到前端有自己需要处理的业务数据。

    笔者曾经在做项目的过程中接触过一个前辈们写的js代码,里面多数都是涉及到需要前端处理的业务数据之类的操作,其中就有一个js文件中的代码行数达到了数万行,而且这样的文件还不止一个,然后带来的问题就是,因为文件代码量过大,有些不规范的写法无法查找出来,导致文件无法被压缩,而且代码复杂度相当高,修改代码的时候很容易就出现一处修改处处bug的情况,导致项目严重延期。前期写的时候大家都可以随心所欲的写,写的很快,后面就变成了系统各种问题频发,严重拖延系统开发进度,无法应对需求变更。

    谈到这里,我们的重点应该可以出现了,就是该如何去设计这个架构,让前端代码能看起来更加清晰易维护,便于多人协作开发,降低系统复杂度。前面我们说了,vue能帮我们实现的更多偏向于视图层,如果我们将业务代码与视图层代码全部写到一个vue文件中,那么这个文件将可能变得非常庞大,同样导致系统的复杂度变高,变得难以维护,业务代码之间的复用性也会变低,导致冗余代码过多。

    有人可能就会问了,废话了一大堆,能不能来点实在的,你说说怎么解决吧。在这里,笔者就上面的一些问题,在这里提出一套自己设想的架构方案,希望能解决目前大部分项目中遇到的这些问题。

    首先还是利用分层的思想,实现代码的逻辑分层,然后结合vue和ES6的优势,将代码模块化,使得整个代码结构清晰明了,易维护。首先笔者将各个分层结构展示给大家:


该结构整体是由vue-cli进行创建,vue-cli生成的代码在这里就不做过多的解释,我们重点放在src文件夹中。

assets:

    静态资源存放目录。主要就是存放系统需要用到的一些静态资源文件,如果有需要的情况下可以在里面建立子文件夹,便于区分静态资源属于哪个页面或者哪个模块。

components:

    组件存放目录。主要存放各个模块需要用到的vue组件,组件的划分应该有:公共组件、模块组件,命名规则可以如下:

    +components
        -common

        -module(模块名称,如:login)

    组件的命名规则应该让人能很容易判断出来该文件的类型属于组件,如:HelloWorldComponent.vue。

consts:

    静态变量存放目录。主要存放系统需要利用到的各种静态变量,如:后台请求的路径。在其他语言开发中,有一点是非常忌讳的,那就是魔法值,魔法值会使代码的可读性降低,增加维护成本;而且一旦变量不只在一个地方用到,在多处或者多个文件,一旦涉及到该变量的变更就会导致多处修改,又增加了维护成本,容易导致新的Bug出现。

下面给出一个命名规则示例代码:

/**
 * 以类的方式进行封装,类名的命名规则为变量作用类型+Constant,容易一眼看出来该类的用处和作用范围
 */
class RequestConstant {
	/**
	 * 根据process.env.NODE_ENV的值确定网关地址,可以保证两种环境下的切换,避免了因为人为忘记修改该值,导致部署生产环境时出错
	 */
	static DOMAIN_URL = process.env.NODE_ENV == 'development' ?
		'我是开发环境下的配置' : '我是生产环境下的配置';
	/**
	 * 请求的路径
	 */
	static SAY_HELLO_PATH = '/mock/sayHello';
}

export default RequestConstant;
libs:

    库文件存放目录。在开发过程中总会遇到需要引入一些其他外部的库,而这些库在npm仓库中却又不存在,这个目录的用处就主要用来放置这些库文件。

mock:

    mock模拟数据接口目录。用于提供模拟数据,方便测试。

plugins:

    vue插件存放目录。主要存放一些比较通用的插件,命名规则可以如下:

    插件用途+Plugin,如:ErrorMsgPlugin.js

router:

    vue路由文件存放目录。一般一个就够,当然,如果系统够大,那么拆分也是有必要的,如果拆分路由文件的话,建议采用模块名+Router的方式。

services:

    业务逻辑层。这层的主要目的就是为了将视图层与业务逻辑层解耦而划分出来,因为vue所代表的视图层已经承担了不少视图层的工作,不应该还让视图层承担业务逻辑层的工作,如:

    视图层需要加载后台请求的数据,请求之后还需要做判断是否成功,返回的结果是否满足视图层的需要,如果不满足是否需要再做转换,这一系列的业务操作如果放在视图层,那么视图层就会变得非常庞大。业务层的文件划分应该更偏向于模块,而视图层文件则是根据项目需求的页面来定,一个模块可以只有一个页面也可以同时拥有几个页面。业务层应该具备这种复用性,方便模块之间调用,如果将业务层代码直接写入视图层,那么业务层的复用性直接就被降低,还会让视图层显得非常臃肿,代码维护难度也随之上升。假设A、B两个页面需要同时调用一段相同的方法,而这个代码不会涉及到任何视图相关的内容,如果在A、B页面里面都写上这段代码,那么一旦涉及代码修改,带来的变动就不再是单纯的一个页面,而是A、B两个,倘若这段代码使用率极高,那么带来的问题将会是巨大的。

    在设计的过程中,我们还应该考虑两个原则:单一职责原则、最少知识原则。如果视图层与业务层耦合,那么视图层和业务层的职责不在单一,同时也违背了对象之间最少了解的原则。

下面给出一个业务层设计参考代码:

import RequestUtil from '../utils/RequestUtil'

import RequestConstant from '../consts/RequestConstant'
/**
 * 模块名+Service的命名规则
 */
class HelloWorldService {

	/**
	 * 初始化实例变量
	 */
	constructor(_this) {
		this.requestUtil = new RequestUtil();
		this._this = _this;
	}
	/**
	 * 业务逻辑层的方法
	 * 视图层只需要知道调用该方法就行,至于该方法里面的代码是如何成功实现返回是不需要视图层关心的
	 */
	sayHello() {
		console.log(this._this);
		return 'hello';
	}

	/**
	 * 该方法涉及到了异步回调,回调函数只需要关心回调的结果是视图层想要的
	 * RequestConstant.SAY_HELLO_PATH接口路径变量其实是业务层需要知道的,视图层则是不需要关心数据是从哪个接口过来的
	 */
	sayAsyncHello(callBack) {
		this.requestUtil.getRequest(RequestConstant.SAY_HELLO_PATH, {}, function(data) {
			let newObjArr = new Array();
			newObjArr.push(data); //转换数据格式,满足视图层需要
			callBack(newObjArr);
		});
	}

}

export default HelloWorldService;

store:

vuex状态管理器。如果模块较多可以按模块划分子文件夹,子文件较多的情况下命名规则建议:模块名+Store

utils:

工具类存放目录。主要放一些系统需要用到的通用小工具。如:http请求工具类、日期格式化工具类等。

views:

视图文件存放目录。如果视图较多可以划分模块子文件夹。


以上内容都是笔者自己在进行项目开发过程中总结的一些经验,当然了,有可能会存在一些不太合适的地方,欢迎大家一起对前端架构进行探讨,创造更加完美的前端架构设计方案。

示例源码下载地址:http://download.csdn.net/download/u010520626/10265009







Logo

前往低代码交流专区

更多推荐