【前端架构】单页面应用设计

1,475 阅读3分钟

一、前端MVC架构

  1. Model模型层:获取、存放所有的对象数据
  2. View表现层:呈现信息给用户
  3. Controller控制层:模型和视图间的纽带

二、MVC架构原理

  1. 简单Model层实现

    function Model () {
      this.text = 'hello';
    }

  2. 简单Controller层实现

    function Controller (model) {
      var that = this;
      this.model = model;
      this.handleEvent = function (e) {
        e.stopPropagation();
        switch(e.type) {
          case 'click':
            that.handleClick(e.target);
            break;
          default:
            console.log(e.target);    
        }
      }
      this.getModelByKey = function (modelKey) {
        return that.model[modelKey];
      }
      this.handleClick = function (target) {
        that.model.text = 'world';
        target.innerText = that.getModelByKey('text');
      }
    }

  3. 简单View层实现

    function View = function (controller) {
      this.controller = controller;
      this.demoButton = document.getElementById('demo-button');
      this.demoButton.innerText = this.controller.getModelByKey('text');
      this.demoButton.addEventListener('click', controller);
    }

  4. 简单MVC调用逻辑实现

    function main () {
      var model = new Model();
      var controller = new Controller(model);
      var view = new View(controller);
    }

三、设计双向绑定的MVC

  1. Model层优化:通过数据劫持实现观察者模式

    function Model () {
      var that = this;
      var text = "hello";
      this.listeners = [];
      Object.defineProperty(that, 'text', {
        get: function () {
          return text;
        },
        set: function (value) {
          text = value;
          that.notify();
        }
      });
    }
    
    Model.prototype.subscribe = function (listener) {
      this.listeners.push(listener);
    };
    
    Model.prototype.notify = function (value) {
      var that = this;
      this.listeners.forEach(function (listener) {
        listener.call(that, value);
      });};

  2. View层优化:当数据发生变更的时候,实现对应View的更改

    function View (controller) {
      var that = this;
      this.controller = controller;
      var elements = document.querySelectorAll('[data-tw-bind]');
      elements.forEach(function (element) {
        if (element.type === 'button') {
          element.innerText = controller.getModelByKey('text');
          that.call = function (data) {
            element.innerText = data.text;
          };
          element.addEventListener('click', controller);
        }
      });
      this.controller.model.subscribe(this);
    }

  3. Controller层:通过clickHandler方法对数据进行更新

    function Controller (model) {
      ...
      this.clickHandler = function (target) {
        this.model.text = 'world';  
      }
    }

四、前端框架选型

  1. 考虑因素
    • 框架是否能满足大部分应用的需求
    • 框架的生态是否丰富
    • 框架的社区支持度怎么样
    • 框架的替换成本如何
    • 团队成员是否能快速掌握该框架
    • 框架对浏览器的支持程度如何
    • 框架维护成本和难度如何 
  2. React
    • Virtual DOM
    • 组件化
    • 思想不局限于前端领域,还有React Native、React VR、SSR等
    • 生态丰富
    • 仅充当View层,构建完整的应用还需要结合路由库、执行单向流库、web API调用库、测试库、依赖管理库等
  3. Angular
    • 大而全
    • 开箱即用
    • 规范性,提供了一系列开发规范和指南
    • 出现问题不易修改
    • 跨平台:NativeScript、Ionic
  4. Vue
    • 易上手
    • 对多页面应用友好,可直接通过引入vue.min.js进行使用
    • 中文文档丰富
    • 跨平台:weex等

五、启动前端应用

  1. 创建应用脚手架
    • 通用的业务相关模块,比如登录、授权、Token管理
    • 页面模板页,比如首页
    • 业务模板,比如中后台应用模板
    • 持续部署脚本,比如持续集成、部署脚本
    • 常用的依赖,比如UI组件库
  2. 构建组件库
    • 选择组件库
      • 是否需要跨框架的组件库
      • 组件库是否容易替换
    • 创建组件库
      • 寻找一个现成的组件库
      • 构建出组件库的基础设施,从找出来的组件库中删除所有的组件,修改项目名等
      • 编写一两个测试组件,引入项目中进行测试
      • 持续不断地更新组件库
  3. 考虑浏览器的支持范围
    • JavaScript兼容(polyfill)
    • CSS兼容
    • 响应式支持
    • 分辨率适配
    • 移动设备适配

六、服务端渲染

  1. 非JavaScript语言的同构渲染
  2. 基于JavaScript语言的同构渲染(以Vue.js为例)

    const Vue = require('vue');
    const server = require('express')();
    const renderer = require('vue-server-renderer').createRenderer();
    server.get('*', (req, res) => {
      const app = new Vue({
        data: {
          url: req.url
        },
        template: `<div>访问的URL是:{{ url }}</div>`
      });
      renderer.renderToString(app, (err, html) => {
        if (err) {
          res.status(500).end('Internal Server Error');
          return;
        }
        res.end(`
          <!DOCTYPE html>
            <html>
              <head>
                <title>
                  Hello
                </title>
              </head>
              <body>
                 ${html}
              </body>      
           </html>
        `)
      });
    });
    server.listen(8080);

  3. 预渲染
    • 爬虫生成静态页面:在本地运行应用,用爬虫抓取所有页面,再上传到文件存储服务器即可
    • 程序生成静态页面:在本地运行应用,内部带有真实的线上数据,由PhantomJs/Chrome Headless来渲染页面,再保存为对应的页面
    • 静态站点生成器:编写一个独立的应用程序,该应用程序将从服务器获取数据,再通过模板来渲染出静态页面

推荐阅读

  1. 上篇:【前端架构】多页面应用设计

参考资料

  1. 《前端架构:从入门到微前端》

微信公众号“前端那些事儿”