前端架构设计

为什么前端需要架构设计

网页的发展史

网页在一开始诞生的时候是纯静态的,是为了方便科学研究人员查看文档、上传论文来使用。

随后 css 的发布,让网页变得多姿多彩,不再局限于浏览器本身的样子,但是随着网页从学术逐渐面向大众,纯静态的网页 就暴露出了它的局限性。于是开始出现了动态页面。

后端主导的阶段

相信大部分人对这种开发模式都十分熟悉,大名鼎鼎的 MVC 架构甚至沿用至今,无数优秀的框架和语言涌出。此时,开发的结构 是这样的:

controller 接收 request 请求,将信息分发给 view ,并且可以通过模板引擎渲染 view 来使用 Model 中的数据,此时的开发 模式主要是由网页开发人员根据原型做出设计图,开发出静态的 html 页面,再由后端人员修改成 jsp 页面,并集成模板。然而这种 开发模式也存在大量的问题。

  • 网页开发人员不懂业务代码,设计出的文档结构,并不能完全和业务契合。
  • 后端人员不懂 html 和 css ,经常会因为套入模板导致样式错乱。

这样的合作方式显然是不愉快的。

前后分离阶段

不论是由前端编辑 jsp 还是让后端编辑 html 都是很棘手的方式,因此前后分离应运而生。此时前端的概念逐渐产生,前端工程师编写 单页应用,通过 ajax 的方式与后端进行交互,后端人员不再考虑 view 层,直接渲染前端静态资源。

现在 view 层和 controller 完全分离了,但是前后端的分工并不明确。后端接收返回的资源是多样的,但是前端人员并不清楚同样的一个 获取用户信息的接口是以什么样的资源来返回,未知是很痛苦的,因此到目前的阶段,前后端并没有完全分离开,接口调试也引起了分歧。

RESTFUL

由于前后分离的交界处是接口,因此有必要对接口进行规范化,两端都遵循这个规范来处理,而不必和不同的人讨论出不同的解决方案。 改良传统的返回码 200,规定了多种返回类型,让接口返回类型更准确的表达出错误信息。定位资源使用 http 动词来描述,避免 uri 中 出现 getUser等方式。

前端框架

到目前为止,后端的项目架构优化了许多,将整个 view 卸下,而诞生的前端还没有很好的认清当前的位置,除了更新资源以外,还按照着 静态资源的模式来开发 html ,存储、项目生命周期、路由都没有处理,仍然在利用后台框架的便捷方式,处理路由跳转,存储 session 信息。。。 随着前端的逐渐发展,大家突然意识到,前端开发人员失去了对前端界面的控制,无法保证静态资源部署以后的功能。无论何时,不可控是非常 可怕的事。 前端框架应运而生,框架构建了前端项目完整的生命周期,路由控制,状态管理,完全解除了后台的“控制”,对于前端开发人员,项目变得可控了。 此外为了从开发阶段到生产阶段的过度,显然存在大量的工作,而这些工作往往是反复、繁琐的,因此工具必不可少,项目构建工具由此产生, 同时也分离了开发、生产模式,为前端的开发、调试提供了极大的便利。

关注点的改变

随着互联网的发展,用户也从一开始的只关注功能逐渐开始对体验更加重视。而此时项目的重心也逐渐向前端偏移,决定数据的往往不是后台架构 本身,而是用户,或者说界面。相对于稳定的服务端数据资源,大量的需求变更是灾难性的,几乎不可能对传承几十年的架构做大的调整,对此 的做法往往都是加接口、抽象表。。。导致的结果就是:表面上看起来风平浪静,但是却暗流涌动。可以说重用性几乎为 0 。但是出现这样的结果 往往是:

TIP

前端:这个数据用不了 后端:你等等我改下 OR 这个是封装在框架内,我改不了

没有设计的垒砖往往是破坏性的,之后的每一块砖都偏离了搭好的中垂线。优秀的后端开发者往往会设计好合理的表结构,不同的场景下只需要将不同的 model 聚合到一起,单独为页面提供数据支持,而初始的架构和表结构,是不会因为页面的改变轻易变动的。目前最好的方式是再分理出 controller 层, 由前端人员使用 nodejs 控制,这样就完全分离了界面对后端的影响,但是受目前大环境的因素,会使用 nodejs 、拥有前端架构设计能力的人远远不够。 因此往往是前后端人员配合来模拟 controller 的分离。

怎样设计当前的前端架构

随着移动互联的普及,移动端以及 PC 端对体验越来越重视,分离出来的"前端"也越来越放大,继续使用原有的开发方式,已经有一种 hold 不住的感觉。 而良好的架构设计,能给设计人员一种掌控全局的感觉。

TIP

相信很多人都经历过这样的场景:

  • 页面内容复杂,有没有注释,动辄几千行代码,读起来异常费劲。
  • 静态资源迁移、项目移植、接口变更,需要通过全局搜索来更改路径。
  • 人员的信息,在一个页面更新,结果其他的界面没有同步。
  • 修改一处代码,结果牵一发动全身。。。
  • 项目开发完成,项目需求变更,整个变更的模块需要重新开发。

模块化

项目只要开始进行就不可能是一成不变的。因此前期的设计尤为重要,如果房子的地基没有打好,那上层的建筑师技艺再高超,也拯救不了整栋楼。前端架构设计 中很重要的一环就是模块化。

模块的封装

  • 低耦合
  • 高内聚
  • 健壮性

和面向对象的封装原则类似,模块的封装需要满足以上三点,来保证复用性。一般来说,通用模块分为两种,业务模块和组件。简单来说的化,当你需要反复复制代码, 那么你可能需要封装一个模块;当你需要反复修改模块的内容时,那么你可能需要拆分一个模块,下面是一个组件的简单案例:

<template>
  <div class="ma-radio">
    <ul class="ificat">
      <li v-for="(item,index) in column" :key="index" :class="{active:index === active}" @click="change(item,index)">
        <img :src="check" />
        <span :dataId="item.id">{{ item.name }}</span>
      </li>
    </ul>
  </div>
</template>

<script>
  import check from 'assets/check.png'
  export default {
    name: 'maRadio',
    props: {
      column: Array,
      value: Number
    },
    watch: {
      value(n) {
        this.active = n
      }
    },
    data() {
      return {
        check,
        active: 0
      }
    },
    methods: {
      change(item, index) {
        this.active = index;
        this.$emit('change', item);
        this.$emit('input',this.active);
      }
    }
  }
</script>
参数 类型 必填
column Array false
active Number false
方法 参数
change obj
input active

封装组件的关键在于输入输出,不需要知道组件内部的运行方式,只关心得到的结果,上述组件可以实现两个功能:

  • 更美观的 radio 展现
  • 无需考虑切换逻辑

问题:

  • 未使用校验工具规范代码 (代码规范)
  • 没有规定传输数组的格式 (健壮性)
  • 没有可选或自定义的图标 (扩展性)

作为项目的组件,已经足够了,如果想要在其他项目中继续使用,那就要继续其他的设计了。

资源分类

资源分类比较容易理解,就是把项目内公共资源统一管理。比如接口、域名、静态资源路径、错误码、国际化等等。良好的归纳也是架构中必不可少的一环。 资源分类管理的关键意义在于把所有可维护的资源内容统一称为单例模式,更改后影响全局的变化,避免在更改过程中有不熟悉的地方遗漏。

状态管理

状态管理和资源管理有些类似。很多数据需要记录当前的状态或多个状态,如果没有统一的管理,就可能会出现 A 页面变化了,B 页面却没有变化。可以把 状态管理理解为一个简易的数据库,我们可以从中读取、更新数据。

常见的状态管理

  • localStorage & sessionStorage
  • indexedDB & WebSQL
  • cookies
  • vuex
  • window

...

项目拆分

当项目逐渐变得复杂时,将动态页面、数据、业务、组件的拆分就显得尤为重要。如果不进行一定的规划,那么单个页面就会显得非常臃肿,并且多个页面间相互影响 或各自为政,即使是一个小 bug 的修改也会牵一发而动全身。

这样每次的修改都是修改一个粒子,并不会影响到其他的逻辑。相对的,抽象出来的公共方法一定要确保函数只做了一件事,这样当这件事的逻辑发生变化,只需要修改 抽象出来的这个方法就可以了。

TIP

封装至关重要,如果一个工具或组件的封装抽象度不够,复用性低或者是 bridge 方法做了过多的事,遇到问题都会让你的代码无从下手。另外,业务逻辑的清晰也 非常关键,如果前端处理了太多复杂的业务逻辑,即使封装的很合理也会给项目的开发带来灾难,这是因为前端需要同时处理显示逻辑组件逻辑和业务逻辑,过于复杂的 业务逻辑会让这三方面同时变得复杂。

架构设计的难点

  • 前端的业务逻辑显示逻辑和组件逻辑息息相关,而页面展现又是需求变更中变化最为明显的一处,因此很只调整页面而不影响到组件和业务几乎不可能。
  • 在 bridge 和 makeData 的封装中,也很难做到完全分离,因此找到最合理的结构需要大量的实践。
  • js 有着特殊的异步函数和交互事件, 相关的操作也需要特殊注意。

展望

正因为有着种种无法完美实现的方案,但是前端的发展是飞速的,在搭建好一套完整的架构后,可以继续进行多方面的优化,比如之前提到的复杂逻辑维护困难的问题,可 以引入 typescript 来增强类型,对每一个函数进行约定,在修改的时候不是靠架构的规范程度而是工具的健壮性控制,让异常可见,然后逐渐排出异常。数据和业务逻辑 也同样可以通过增加 node 中间件来移除页面、组件和业务的耦合。同时也已增加测试模块来保证不破坏其他人的有效代码。