7月阅读周·前端架构:从入门到微前端 | 架构设计:单页面应用

举报
叶一一 发表于 2024/07/23 15:01:58 2024/07/23
【摘要】 背景去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。没有计划的阅读,收效甚微。新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。这个“玩法”虽然常见且板正,但是有效,已经坚持阅读六个月。已读完书籍:《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScri...

背景

去年下半年,我在微信书架里加入了许多技术书籍,各种类别的都有,断断续续的读了一部分。

没有计划的阅读,收效甚微。

新年伊始,我准备尝试一下其他方式,比如阅读周。每月抽出1~2个非连续周,完整阅读一本书籍。

这个“玩法”虽然常见且板正,但是有效,已经坚持阅读六个月。

已读完书籍《架构简洁之道》、《深入浅出的Node.js》、《你不知道的JavaScript(上卷)》、《你不知道的JavaScript(中卷)》、《你不知道的JavaScript(下卷)》、《数据结构与算法JavaScript描述》、《WebKit技术内幕》

当前阅读周书籍《前端架构:从入门到微前端》

架构设计:单页面应用

在业务不断发展的过程中,由于前端项目变得越来越复杂,所以我们要考虑拆分出前端应用部分,使之成为一个能独立开发、运行的应用,而非依赖于后端渲染出HTML的多页面应用。我们不得不采用更符合复杂客户端、UI层开发的MVC架构,以便更快速地开发前端应用。前端应用采用了MVC框架,也变成了单页面应用,前后端便开始分离了。

前端MV*原理

前端MVC架构的三个层次的作用:

  • Model(模型层),用来获取、存放所有的对象数据。
  • View(表现层),呈现信息给用户。
  • Controller(控制层),模型和视图之间的纽带。

就前端而言,当我们更新数据模型(Model)时,就需要通过控制器(Controller)来更新UI(View)。想要更好地了解它们的关系,可以先来看一个前端MVC架构的示例。

前端MVC架构原理

了解前端MVC最好的形式,便是从代码中来看MVC架构。

一个与MVC架构相关的示例:单击页面上的一个名为hello的按钮,然后将按钮的显示形式变成world。功能很简单,但是要以MVC的形式来呈现。如果以非MVC的形式来呈现,那么代码如下:

<button id="demo-button"></button>
<script>
  var text = 'hello';
  var demoButton = document.getElementById('demo-button');
  demoButton.innerText = text;
  demoButton.addEventListener('click', {
    handleEvent: function (event) {
      event.target.innerText = 'world';
    },
  });
</script>

在上面的代码中,初始化时会更新按钮的显示文字,单击按钮时也会更新按钮上的文字。为了方便后面的代码解释,我们传入的addEventListener变量是一个带handleEvent参数的变量,而非一个函数,两者的效果是等价的。

由于我们要修改的是按钮背后的文字,所以需要将文字变成一个变量,存于Model函数中。对应的Model函数代码如下:

// 代码位于chapter06/basic-mvc目录下
function Model() {
  this.text = 'hello';
}

在这个Model函数里,我们创建了一个text对象,在text对象中存储了一个“hello”字符串,所以Model函数的作用是:提供和存放数据。

随后,在我们的MVC架构中,还需要有一个Controller实现:

//代码位于chapter06/basic-mvc目录下
function Controller(model) {
  var that = this;
  this.model = model;
  this.handleEvent = function (e) {
    e.stopPropagation();
    switch (e.type) {
      case 'click':
        that.clickHandler(e.target);
        break;
      default:
        console.log(e.target);
    }
  };
  this.getModelByKey = function (modelKey) {
    return that.model[modelKey];
  };
  this.clickHandler = function (target) {
    that.model.text = 'world';
    target.innerText = that.getModelByKey('text');
  };
}

Controller主要包含三个函数:

  • handleEvent,用于响应相关的事件。
  • getModelByKey,返回对应的模型数据的值。
  • clickHandler,用户单击后对应的操作。

在有了Controller之后,还需要实现一层View,代码如下:

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

View层实现了相应的初始化和事件绑定。初看应该在Controller进行绑定,但是有了双向绑定之后,都是由View层来自动绑定的。

最后,我们将它们(Model、View、Controller)结合到一起,便是一个粗糙的MVC示例:

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

以这种MVC的方式实现事件的响应和逻辑处理,有助于帮助我们处理复杂的前端应用。但是由于不可能在每个小的部分中都以这种方式去实现每个功能,所以相关的操作变得非常烦琐。而且,当我们拥有大量的交互时,使用原生的JavaScript来开发应用,代码将变得难以维护。这时,我们需要用前端框架来解决:

  1. 保持UI与模型的同步(即双向绑定)的问题。
  2. 原生的JavaScript难以编写复杂、高效、易维护的UI的问题。

进阶:设计双向绑定的MVC

实现一个观察者模式,即定义对象间的一种一对多的依赖关系,使得每当一个对象状态发生改变时,其相关的依赖对象皆得到通知并被自动更新。

为此,我们需要在MVC应用里添加两种类型的观察者模式:

  • 基于数据劫持的观察者模式。当数据模型的值发生变化时,调用对应的函数。
  • 常规的观察者模式。提供可供调用的调用方式。

当数据模型发生变化时,对Model函数进行修改,代码如下:

//代码位于chapter06/basic-mvc-twb目录下
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);
  });
};

数据更新(Setter)的相关函数被抽取出来并放到notify函数中。一旦某个数据的值发生变化,就会触发相应的监听者的调用逻辑。而在View层里,我们则会进行之前的双向绑定逻辑,并注册成为一个订阅者,以便在数据发生更新的时候更改对应的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);
}

与之前的双向绑定类似,我们会遍历所有的data-tw-bind属性,为button类型的元素的初始化值自动绑定click事件。同时,添加了订阅的响应函数call,当Model层的数据发生变化时更新button上的显示文本。此外,将View注册为数据模型的监听者。

最后,Controller通过clickHandler方法对数据进行更新。

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

当这个数据发生变化时,会调用数据劫持Setter中的相关notify方法。也就会调用所有订阅者的响应函数call方法,并且还会更新按钮上的文字。为了验证双向绑定是可以工作的,我们可以在Controller中对setTimeout进行设置,让其在3秒后改变model的值,以观察对应的文本是否更新:

setTimeout(function () {
  that.controller.model.text = '3s';
}, 3000);

前端MV*框架的原理就是通过观察和订阅来进行联动操作,以自动触发各种逻辑函数。

前端框架选型

选定一个前端框架时,我们还要考虑的因素有:

  • 框架是否能满足大部分应用的需求?如果不能,那么需要使用哪个框架?
  • 框架是否有丰富的组件库?如果没有,我们的团队和组织是否有独立开发的能力?
  • 框架的社区支持怎样?在遇到问题时能否快速方便地找到人解答?
  • 框架的替换成本如何?假如我们的新项目将使用B框架,那么我们还需要额外学习什么内容?

启动前端应用

选择好前端框架后就可以进行下一步开发了:

  1. 寻找合适的脚手架,编写出第一个“Hello, world”。
  2. 选择合适的UI框架,以快速开发前端页面。
  3. 确认浏览器的支持范围,以明确测试边界。
  4. 明确响应式设计的需求,以明确在编码的过程中支持哪些设备。

这部分只是那些独立于组织、公司的通用内容。对于流程规范化的组织内部,还会更复杂。

服务端渲染

单页面应用的服务端渲染与纯服务端渲染有所不同,多数情况下,单页面应用的服务端渲染依赖于服务端拥有一个JavaScript引擎,由其运行JavaScript后返回相应的模板。但是,当服务端所使用的语言拥有相同的模板引擎时,也可以不需要这种方式。依笔者的开发经验来看,单页面应用的服务端渲染可以分成三种类型:

  • 非JavaScript语言的同构渲染。
  • 基于JavaScript语言的同构渲染。
  • 预渲染。

每种方式都各有特色,也各自适合不同的场景。

总结

本篇文章的核心内容:

  • 结合MVC框架应用介绍了MVC架构的基本原理,并结合双向绑定的内容开发了一个支持双向绑定的MVC应用/框架。
  • 前端应用介绍从寻找合适的脚手架开始,到选择合适的UI框架,再到确认浏览器的支持范围、明确响应式设计的需求等。

作者介绍
非职业「传道授业解惑」的开发者叶一一。
《趣学前端》、《CSS畅想》等系列作者。华夏美食、国漫、古风重度爱好者,刑侦、无限流小说初级玩家。
如果看完文章有所收获,欢迎点赞👍 | 收藏️ | 留言📝

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。