前端架构设计的思考

前言

我认为学习编程就是这样一个过程。刚开始是语法的应用,主要时间都用来做数据类型转换、处理报错问题;后来语法比较熟悉了报错也知道解决办法了,这时候就是框架的使用,怎么配置、怎么启动等;在这学会之后这时就不再是简单的工程问题了,而是到了解决问题的阶段,即怎么把一个问题抽象成对象,怎么将它用编程语言表达出来;这部分初步有了认识后就会考虑怎么设计问题框架能将相似的问题使用这个框架一并解决、怎么设计架构使问题更简单、更快的解决。

经过语言和框架学习之后,最近对于前端开发有了新的思考:为什么要这么写? 这几年做前端带给我的思考虽然没到怎么能设计出一个完整的架构,但我可以理解了现有一些设计的理由。这个文章就是总结前端架构中部分设计理念的来源。

《前端架构设计》这本书提到的 4 方面设计架构:代码、流程、测试、文档。我主要做的是代码和流程还有我的一些架构选型,这篇文章会主要讲我做过这几个方面。

代码

开发规范文档

关于开发规范文档,各个大厂团队都有自认为的最佳实践,没有孰对孰错,而是有了规范可以团队开发中一定程度上避免一些语法错误,也可以从中看出前辈总结的经验。

HTML

  • 程序式标记:由页面模板创建,比如 WordPress、CMS 等,自动化程度高,但不利于修改和维护(已弃用)
  • 静态标记:由程序员自己创建。例如<div class="nav"></div>改为<nav></nav>,更加语义化,但自动化程度低。
    缺点:
    <header>
      <section>
        <nav>
          <div>
              <ul>
                <li><a href="#"></li>
              </ul>
          </div>
        </nav>
      </section>
    </header>
    <style>
        header > section > nav > div > ul > li {
            margin: 0;
        }
        header > section > nav > div > ul > li > a {
            color: red;
        }
    </style>
    1. 样式选择器十分复杂
    2. 不利于改变 HTML 结构,CSS 难以维护
  • 模块化标记(BEM 原则):由模板创建,但命名由程序员决定。例如
    <nav class="nav">
      <ul class="nav__continer">
        <li class="nav__item">
          <a href="/" class="nav__link"></a>
        </li>
      </ul>
    </nav>

CSS

命名风格

  • OOCSS(Object-Oriented CSS):
    原则:
    • 分离结构与外观:将视觉特性定义为可复用的单元。例如simple样式就是 div 使用直角,而complex可能使用圆角等。
    • 分离容器与内容:css 选择器只选择可复用的类名,而不管元素是什么。

    举例:
    <!-- OOCSS -->
    <div class="toggle simple">
      <div class="toggle-control open">
        <h1 class="toggle-title">Title 1</h1>
      </div>
      <div class="toggle-details open">....</div>
    </div>
  • SMACSS(Scalable and Modular Architecture for CSS,模块化架构可拓展的 CSS): 原则:
    • 基础:不添加类名,会有默认样式实现
    • 布局:页面分成区域
    • 模块:设计中的模块化,可复用单元
    • 状态:特定状态下的样式,例is-active
    • 主题:可更换的视觉风格样式,例simple

    举例:(BootStrap 就是一个例子)
    <!-- SMACSS -->
    <div class="toggle toggle-simple">
      <div class="toggle-control is-active">
        <h2 class="toggle-title">Title 2</h2>
      </div>
      <div class="toggle-details is-active"></div>
    </div>
  • BEM(Block Element Modifier,块元素修饰符):
    BEM 是 SMACSS 的一个方面,是一个 css 命名规则
    原则:
    • 块名:组件名称
    • 元素:元素在块里的名称
    • 修饰符:任何与块级元素相关联的修饰符
    • 即命名:块名__元素--修饰符

    举例:
    <!-- BEM -->
    <div class="toggle toggle--simple">
      <div class="toggle__control toggle__control--active">
        <h2 class="toggle__title">Title 2</h2>
      </div>
      <div class="toggle__details toggle__details--is-active"></div>
    </div>

注:这几种风格可以混合使用。

设计原则

  • 单一职责原则
    该原则规定,创建的样式必须单一,高度聚焦;目的单一。
    例:
    .padding-10 {
      padding: 10px;
    }
    .padding-20 {
      padding: 20px;
    }
  • 单一样式来源
    该原则将单一职责原则应用到更深层次,每个 css 类名单一用途的同时,每个标签的样式也只有单一的来源。任何组件的设计必须由组件本身决定,不能被其父类元素类名限制。即一个类名可以完全控制这个类的样式,不会受其父类影响
    例:
    <div class="blog">
      <h2 class="blog-header">This is Blog header.</h2>
      <div class="calendar">
        <h2 class="calendar-header">This is calendar header.</h2>
      </div>
    </div>
    <style>
      /* calendar css */
      .calendar-header {
        color: red;
        font-size: 2em;
      }
      /* blog css */
      .blog-header {
        color: black;
        font-size: 2.4em;
      }
      /* public css */
      .blog .calendar {
        font-size: 1.5em;
      }
    </style>

    问题:共用的样式无处安放,所以有了如下改进:带上下文的样式
    <style>
      /* calendar css */
      .calendar-header {
        color: red;
        font-size: 2em;
      }
      .blog .calendar-header {
        font-size: 1.5em;
      }
      /* blog css */
      .blog-header {
        color: black;
        font-size: 2.4em;
      }
    </style>

    若采用 BEM 可以在上下文样式中加入带状态的组件样式。

流程

早期工作流程

早期是没有前端这个分支的,因为数据是来自后端,只要想个办法让服务器渲染出数据就可以了,所以前期的互联网的页面排版像报纸一样。随着样式表的出现和交互性的增强,后端要做的事情越来越多,就逐渐成为专注搞页面的前端了。

在前端成为前端之前,这个群体被称为网站美工,这个称呼就是源自于工作流程。

需求 => 平面web设计(psd标注稿) => 前端代码还原

甚至有了交互设计师

需求 => 平面web设计(psd标注稿) => 交互设计稿(Axure) => 前端代码还原

现代开发工作流程

但现代前端随浏览器的发展和需求的复杂化而进步,其发展非常迅速,这种工作流程就存在天生的不足,因为它导致页面的代码组织结构复杂,前期沟通需求频繁变化,网站一经设计对后续更新不友好。所以现代前端是这样的模式:

需求 => 原型设计 => 前端开发

在这个工作流程中,需求需要产品经理来做需求分析,需要各方面设计来进行原型设计,最后交给前端进行开发,这样将各个环节(需求、设计、代码)完全分离开。

需求

需求分析阶段需要设计、视觉、前端、后端 4 个方面的人共同分析总结。

原型设计

现代前端的开发流程中,原型设计是最大的变化。参与原型设计阶段的人是交互设计、视觉设计等,在需求全部确定,原型确认采纳后,才交给前端进入开发环节。而以前的代码写好后如果需求变动,代码可能也要跟随着改动。

前端开发

图中的措辞也有变化,原来的代码还原变成了前端开发,也就是说前端不再是单纯的根据图片进行代码还原,而是根据原型设计自行设计结构开发出符合要求的页面。这里就体现了前端与美工的区别:美工不需要进行抽象的思考,只要像素级还原就可以;而前端需要考虑选型、性能、测试和迭代等方面同时要符合需求和原型的要求,同时要应用软件开发流程,开发后需要进行测试部署上线。

版本控制

开发中使用版本控制(以 Git 为例)的一种实践思路:

开发中不操作 master 分支,建立代码库功能分支(feature branch)后合并到 master:

这样做的好处:

  1. 提交给 master 分支的总是完整的代码。
  2. pull request 提交给 master 之前有一步审核
  3. 可以单独测试

发布

发布的原则是只提交尽可能少的代码,忽略临时文件、编译代码的资源文件、Node 模块等。

这时有一类代码就是“功能所需”且“会引起冲突”,这就是编译后的资源文件,例如 Stylus 编译后的 css 等。如果不添加到源码中会造成功能缺失,添加到源码中却可能导致后续文件冲突。处理这类文件可以单独进行处理:持续集成(Travis CI)这个服务可以在代码前自动对代码进行一些处理。

另外,可以引入自动化任务处理该部分流程,如 Grunt、Gulp 等。

测试

单元测试(针对功能、数据、兼容性)

单元测试的编写原则:简单而完备。即一次只做一件事,将功能细分为几个函数分别测试。

视觉还原测试(针对设计稿的还原情况)

这部分较多为测试工具,比如 PhantomJS 等。

页面架构

17 年我初学时暑期实习的作品现在来看属于 MVP 模式,前后端简单分离,部分页面与数据库交互获取数据并渲染;18 年时我进行了前后端真正的分离,有了前端 MVC 的实现;19 年毕业设计使用 Vue 完成了一个单页 SPA 应用。那么就产生了这个哲学问题,我为什么要这样?前端圈为什么会这样发展?

我知道一说这几个词又要开始讲历史了,现在的前端峰会演讲仿佛不讲历史都不知道怎么开场了一样…(没错,我说的就是GMTC还有 TFC)…所以这里就不啰嗦了,想知道前端发展沿革的可以看这篇文章:跟著小明一起搞懂技術名詞:MVC、SPA 與 SSR - 前端 - 掘金

这节主要想考虑的是关于前端架构设计:MPA、SPA、SSR 以及它们的取舍。

MPA 多页面应用

每一次页面跳转的时候,后台服务器都会给返回一个新的html文档,这种类型的网站也就是多页网站,也叫做多页应用。

  • 首屏时间快
    首屏时间叫做页面首个屏幕的内容展现的时间,当我们访问页面的时候,服务器返回一个 html,页面就会展示出来,这个过程只经历了一个 HTTP 请求,所以页面展示的速度非常快。
  • 搜索引擎优化效果好(SEO)
    搜索引擎在做网页排名的时候,要根据网页内容才能给网页权重,来进行网页的排名。搜索引擎是可以识别 html 内容的,而我们每个页面所有的内容都放在Html中,所以这种多页应用,seo 排名效果好。
  • 页面切换慢
    因为每次跳转都需要发出一个 http 请求,如果网络比较慢,在页面之间来回跳转时,就会发现明显的卡顿。

SPA 单页面应用

第一次进入页面的时候会请求一个html文件,刷新清除一下。切换到其他组件,此时路径也相应变化,但是并没有新的html文件请求,页面内容也变化了。

原理是:JS会感知到url的变化,通过这一点,可以用 js 动态的将当前页面的内容清除掉,然后将下一个页面的内容挂载到当前页面上,这个时候的路由不是后端来做了,而是前端来做,判断页面到底是显示哪个组件,清除不需要的,显示需要的组件。这种过程就是单页应用,每次跳转的时候不需要再请求 html 文件了。

  • 页面切换快
    页面每次切换跳转时,并不需要做html文件的请求,这样就节约了很多http发送时延,我们在切换页面的时候速度很快。
  • 首屏时间慢,SEO 差
    单页应用的首屏时间慢,首屏时需要请求一次html,同时还要发送一次js请求,两次请求回来了,首屏才会展示出来。相对于多页应用,首屏时间慢。
    SEO 效果差,因为搜索引擎只认识html里的内容,不认识js的内容,而单页应用的内容都是靠js渲染生成出来的,搜索引擎不识别这部分内容,也就不会给一个好的排名,会导致单页应用做出来的网页在百度和谷歌上的排名差。

MPA 与 SPA 区别

/多页面应用模式MPA单页面应用模式SPA
应用构成由多个完整页面构成一个外壳页面和多个页面片段构成
跳转方式页面之间的跳转是从一个页面到另一个页面一个页面片段删除或隐藏,加载另一个页面片段并显示。片段间的模拟跳转,没有开壳页面
跳转后公共资源是否重新加载
URL模式http://xxx/page1.html
http://xxx/page2.html
http://xxx/shell.html#page1
http://xxx/shell.html#page2
用户体验页面间切换加载慢,不流畅,用户体验差,尤其在移动端页面片段间切换快,用户体验好,包括移动设备
能否实现转场动画容易实现(手机APP动效)
页面间传递数据依赖URLcookie或者localstorage,实现麻烦页面传递数据容易(VuexVue中的父子组件通讯props对象)
搜索引擎优化(SEO)可以直接做需要单独方案(SSR)
特别适用的范围需要对搜索引擎友好的网站对体验要求高,特别是移动应用
开发难度较低,框架选择容易较高,需要专门的框架来降低这种模式的开发难度

SSR

SPA 的应用性能和开发难度都表现不错,但缺点就是对于 SEO 非常不友好。SEO 是搜索引擎抓取的静态文档,但 SPA 由于内容是动态渲染的,大部分内容并不能一次全部显示,尤其是采用懒加载模块运行的网站,因此 SEO 的问题影响网站的排名。为了解决这个问题采用了 SSR 方案,即服务器端渲染。

原理就是原本客户端渲染的网站,由于初次请求文档少对 SEO 不友好,改为服务器端渲染出所有内容后发送,这样 SEO 的爬虫就会抓取到所有内容从而正常排名。

“No Silver Bullet”

"No Silver Bullet"

—— Turing Award winner Fred Brooks in 1986

但 SPA+SSR 也不能用于一切。SPA 对于信息流应用来说没有历史记录,也就是说上一个页面返回的时候会重新获取数据并加载,之前的数据就不存在了。举个例子,知乎网站就不适合全局 SPA,我看到了两个问题,点开其中一个看看,然后再想回来看另一个问题就会发现这个问题已经被刷掉了,造成使用体验的损失。

所以在部分页面中还是要保留 MPA,两者要根据需求搭配使用。

总结

本文总体按《前端架构设计》一书的思路来编写,也融入了我的开发经验。这篇文章总结到这里,未来还会更加深入探索前端开发。