花椒和邻居's Blog 花椒和邻居's Blog
首页
前端
开源
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • Java程序员
收藏
关于
随笔
GitHub (opens new window)

花椒和邻居

工作了三年半的前端实习生
首页
前端
开源
  • 技术文档
  • GitHub技巧
  • Nodejs
  • 博客搭建
  • Java程序员
收藏
关于
随笔
GitHub (opens new window)
  • 前端开发笔记

    • 重识 HTML,掌握页面基本结构和加载过程
    • CSS:页面布局的基本规则和方式
    • JavaScript 如何实现继承?
    • JavaScript 引擎如何执行 JavaScript 代码?
    • 单线程的 JavaScript 如何管理任务?
    • 一个网络请求是怎么进行的?
    • HTTP 协议和前端开发有什么关系?
    • 深入剖析浏览器中页面的渲染过程
    • 改善编程思维:从事件驱动到数据驱动
    • 掌握前端框架模板引擎的实现原理
    • 为什么小程序特立独行
    • 单页应用与前端路由库设计原理
    • 代码构建与 Webpack 必备技能
    • 提升编程体验:组件化与模块化设计
    • AngularReactVue 三大前端框架的设计特色
      • 如何设计合适的状态管理方案
      • 如何搭建前端监控体系为业务排忧解难
      • 如何进行性能分析的自动化实现
      • 前端性能优化与解决方案
      • 如何进行技术方案调研与设计
      • 如何设计一个前端项目
      • 通过前端工程化提升团队开发效率
      • 大型前端项目的痛点和优化方案
      • 如何通过前期准备和后期复盘让项目稳定上线
    • JavaScript

    • 3D地图相关

    • 前端
    • 前端开发笔记
    花椒和邻居
    2022-04-23
    目录

    AngularReactVue 三大前端框架的设计特色

    在前面第 10 讲中,我介绍了前端框架中的核心能力——模板引擎。其实,除了模板引擎以外,前端框架还提供了很多其他的能力,比如性能优化相关、状态管理相关等。现如今,虽然各式各样的框架层出不穷,但目前稳定排行在前的基本上是这三大热门框架 Angular/React/Vue(排名不分先后)。

    对于不同的前端框架来说,各自的设计原理和解决方案都有所不同,开发者可根据自身需要选择合适的前端框架。

    今天,我们就来看一下 Angular/React/Vue 三个框架之间的区别、各自的特点和适用场景等

    Angular/React/Vue 框架对比

    Angular 是一个应用设计框架与开发平台,用于创建高效、复杂、精致的单页面应用,提供了前端项目开发较完整的解决方案。

    与此相对,React/Vue 则专注于构建用户界面,在一定程度上来说是一个 JavaScript 库,不能称之为框架。

    由于 React/Vue 都提供了配套的页面应用解决方案和工具库,因此我们常常将它们作为前端框架与 Angular放在一起比较。

    实际上,三个框架的关系可以简单用这样的公式表达。

    Angular = React/Vue + 路由库(react-router/vue-router) + 状态管理(Redux/Flux/Mobx/Vuex) + 脚手架/构建(create-react-app/Vue CLI/Webpack) + ...
    

    我们先来看看 Angular。

    Angular

    Angular 最初的设计是针对大型应用进行的,上手成本较高,因为开发者需要掌握一些对前端开发来说较陌生的概念,其中包括依赖注入、模块化、脏检查、AOT 等设计。

    依赖注入

    依赖注入并不是由 Angular 提出的,它是基于依赖倒置的设计原则设计出来的一套机制。

    在项目中,依赖注入体现为:项目提供了这样一个注入机制,其中有人负责提供服务、有人负责消耗服务,通过注入机制搭建了提供服务与消费服务之间的接口。对于消费者来说,无须关心服务是否已经被创建并初始化,依赖注入机制会保证服务的可用性。

    在这样的机制下,开发者只需要关注如何使用服务,至于这个服务内部的实现是怎样的、它是什么时候被初始化的、它又依赖了怎样的其他服务,都交给了依赖注入机制来处理,不需要操心。

    Angular 提供的便是这样一套依赖注入系统,可以及时创建和交付所依赖的服务。Angular 通过依赖注入来帮你更容易地将应用逻辑分解为服务,并让这些服务可用于各个组件中。这便是 Angular 中的依赖注入设计。

    前面提到,Angular 的设计是针对大型应用的,使用依赖注入可以轻松地将各个模块进行解耦,模块与模块之间不会有过多的依赖,可以轻松解决大型应用中模块难以管理的难题。所以在 Angular 中,依赖注入配合模块化组织能达到更好的效果。

    模块化组织

    Angular 模块把组件、指令和管道打包成内聚的功能块,每个模块聚焦一个特性区域、业务领域、工作流或通用工具。

    所以我们可以用Angular 模块来自行聚焦某一个领域的功能模块,也可以使用 Angular 封装好的一些功能模块,像表单模块 FormModule、路由模块 RouterModule、Http 模块,等等。

    通过依赖注入的方式,我们可以直接在需要的地方引入这些模块并使用。模块的组织结构为树状结构,不同层级的模块功能组成了完整的应用。通过树状的方式来,依赖注入系统可高效地对服务进行创建和销毁,管理各个模块之间的依赖关系。

    其中,脏检查机制也由于模块化组织的设计,被诟病的性能问题得以解决。

    状态更新:脏检查机制

    什么是脏检查呢?在 Angular 中,触发视图更新的时机来自常见的事件如用户交互(点击、输入等)、定时器、生命周期等,大概的过程如下:

    1. 在上述时机被触发后,Angular会计算数据的新值和旧值是否有差异;

    2. 若有差异,Angular 会更新页面,并触发下一次的脏检查;

    3. 直到新旧值之间不再有差异,或是脏检查的次数达到设定阈值,才会停下来。

    由于并不是直接监听数据的变动,同时每一次更新页面之后,很可能还会引起新的值改变,这导致脏检查的效率很低,还可能会导致死循环。虽然 AngularJS 有阈值控制,但也无法避免脏检查机制所导致的低效甚至性能问题。

    脏检查机制在设计上存在的性能问题一直被大家诟病,在 Angular2+ 中引入了模块化组织来解决这个问题。由于应用的组织类是树结构的,脏检查会从根组件开始,自上而下对树上的所有子组件进行检查。相比 AngularJS 中的带有环的结构,这样的单向数据流效率更高,而且容易预测,性能上也有不少的提升。除了模块化组织之外,Angular2+ 同时还引入了 NgZone,提升了脏检查的性能。

    在 Angular 中除了对脏检查机制进行了性能优化,还提供了其他的优化手段,AOT 编译便是其中一种。

    用 AOT 进行编译

    我们先来介绍下 AOT 编译和 JIT 编译。

    • JIT 编译:在浏览器中运行时编译,视图需要花很长时间才能渲染出来,导致运行期间的性能损耗。

    • AOT 编译(预编译):在构建时编译,使得页面渲染更快,可提高应用性能。

    Angular 提供了预编译(AOT)能力,无须等待应用首次编译,以及通过预编译的方式移除不需要的库代码、减少体积,还可以提早检测模板错误。

    到此,我介绍了 Angular 中的依赖注入、模块化组织、脏检查机制、AOT 编译,这些是 Angular框架设计中比较核心的概念和解决方案。

    除此之外,Angular提供了完备的结构和规范,新加入的成员能很快地通过复制粘贴完成功能的开发。好的架构设计,能让高级程序员和初入门的程序员写出相似的代码,Angular 通过严格的规范约束,提升了项目的维护体验。

    由于 Angular 目标是提供大而全的解决方案,因此相比 Angular,React和 Vue则更专注于用户界面的构建和优化,我们继续来看一下 React。

    React

    React 和 Vue 都是专注于构建用户界面的 JavaSctipt 库,它们不强制引入很多工程类的功能,也没有过多的强侵入性的概念、语法糖和设计,因此它们相对 Angular 最大的优势是轻量。

    而对比 Vue,React 最大的优点是灵活,对原生 JavaScript 的侵入性弱(没有过多的模板语法),不需要掌握太多的API 也可以很好地使用。

    React的哲学是:React 是用JavaScript 构建快速响应的大型 Web应用程序的首选方式。

    接下来,我们来看看React 中的一些核心设计和特色,首选便是虚拟 DOM的设计。

    虚拟 DOM

    虚拟 DOM 方案的出现,主要为了解决前端页面频繁渲染过程中的性能问题。该方案最初由 React 提出,如今随着机器性能的提升、框架之间的相互借鉴等,在其他框架(比如 Vue)中也都有使用。

    虚拟 DOM的设计,大概可分成 3 个过程,下面我们分别来看看。

    1. 用JavaScript 对象模拟 DOM 树,得到一棵虚拟 DOM 树。

    不知道你是否仔细研究过 DOM 节点对象,一个真正的DOM 元素非常庞大,拥有很多的属性值。一个 DOM 节点包括特别多的属性、元素和事件对象,但实际上我们会用到的可能只有其中很小一部分,比如节点内容、元素位置、样式、节点的添加删除等方法。

    所以,我们通过用 JavaScript 对象来表示 DOM 元素的方式,该对象仅包括常用的这些属性方法和节点关系,这样就可以大大降低对象内存、虚拟 DOM 差异对比的计算量等。

    2. 当页面数据变更时,生成新的虚拟 DOM 树,比较新旧两棵虚拟 DOM 树的差异。

    当我们用 JavaScript 对象来模拟 DOM 节点之后,可以构造出虚拟 DOM 树。

    当发生状态变更的时候,可以重新构造一棵新的 JavaScript 对象 DOM 树。通过将新的模拟 DOM 树和旧的模拟 DOM 树进行比较,得到两棵树的差异,并记录下来。在比较之后,我们可以获得这样的差异:

    • 需要替换掉原来的节点;

    • 移动、删除、新增子节点;

    • 修改了节点的属性;

    • 对于文本节点的文本内容改变。

    image.png

    如图所示,我们对比了两棵基于<div>元素的DOM 树,得到的差异有:

    • p 元素插入了一个 span 元素子节点;

    • 原先的文本节点挪到了 span 元素子节点下面;

    经过差异对比之后,我们能获得一组差异记录,接下来我们需要使用它。

    3. 把差异应用到真正的 DOM 树上

    要实现最终的页面渲染,需要进行一些 JavaScript 操作,将差异应用到真正的DOM 树上,例如节点的替换、移动、删除,文本内容的改变等。

    使用这样的方式来更新页面,可以将页面的DOM 变更范围减到最小,同时通过将多个状态变更合并计算,可以降低页面的更新频率。因此,使用虚拟 DOM,可以有效降低浏览器计算和性能。

    虽然虚拟 DOM 解决了页面被频繁更新和渲染带来的性能问题,但传统虚拟 DOM 依然有以下性能瓶颈:

    • 在单个组件内部依然需要遍历该组件的整个虚拟 DOM 树;

    • 在一些组件整个模版内只有少量动态节点的情况下,这些遍历都是性能的浪费;

    • 递归遍历和更新逻辑容易导致 UI 渲染被阻塞,用户体验下降。

    对此,React 框架也有进行相应的优化:使用任务调度来控制状态更新的计算和渲染。

    状态更新:任务调度

    React 中使用协调器(Reconciler)与渲染器(Renderer)来优化页面的渲染性能。

    在 React 里,可以使用ReactDOM.render/this.setState/this.forceUpdate/useState等方法来触发状态更新,这些方法共用一套状态更新机制,该更新机制主要由两个步骤组成。

    1. 找出变化的组件,每当有更新发生时,协调器会做如下工作:

    • 调用组件render方法将 JSX 转化为虚拟 DOM;

    • 进行虚拟 DOM Diff 并找出变化的虚拟 DOM;

    • 通知渲染器。

    1. 渲染器接到协调器通知,将变化的组件渲染到页面上。

    在 React15 及以前,协调器创建虚拟 DOM 使用的是递归的方式,该过程是无法中断的。这会导致 UI 渲染被阻塞,造成卡顿。

    为此,React16 中新增了调度器(Scheduler),调度器能够把可中断的任务切片处理,能够调整优先级,重置并复用任务。调度器会根据任务的优先级去分配各自的过期时间,在过期时间之前按照优先级执行任务,可以在不影响用户体验的情况下去进行计算和更新。

    通过这样的方式,React 可在浏览器空闲的时候进行调度并执行任务,篇幅关系这里不再展开。

    虚拟DOM和任务调度的状态更新机制,是 React 中性能优化的两个重要解决方案。

    除了性能优化以外,React 的出现同时带来了特别棒的理念和设计,包括 jsx、函数式编程、Hooks等。其中,函数式编程的无副作用等优势向来被很多程序员所推崇,Hooks 的出现更是将 React 的函数式编程理念推向了更高峰。

    相比于 Angular,React 的入门门槛要低很多,但提到简单易学,就不得不说到 Vue了。

    Vue

    Vue 最大的特点是上手简单,框架的设计和文档对新手极其友好。但这并不代表它只是个简单的框架,当你需要实现一些更加深入的自定义功能时(比如自定义组件、自定义指令、JSX 等),你会发现它也提供了友好的支持能力。

    很多人会认为 Vue 只是把 Angular 和 React 的优势结合,但 Vue 也有自身的设计和思考特色。这里,我们同样介绍一下 Vue 的设计特点。

    虚拟 DOM

    前面我们在介绍 React的虚拟 DOM时,提到传统虚拟 DOM的性能瓶颈,Vue 3.0 同样为此做了些优化。

    在 Vue 3.0 中,虚拟 DOM通过动静结合的模式来进行突破:

    • 通过模版静态分析生成更优化的虚拟 DOM 渲染函数,将模版切分为块(if/for/slot);

    • 更新时只需要直接遍历动态节点,虚拟 DOM的更新性能与模版大小解耦,变为与动态节点的数量相关。

    可以简单理解为,虚拟 DOM 的更新从以前的整体作用域调整为树状作用域,树状的结构会带来算法的简化以及性能的提升。

    状态更新: getter/setter、Proxy

    在 Vue 3.0 以前,Vue中状态更新实现主要依赖了getter/setter,在数据更新的时候就执行了模板更新、watch、computed等一些工作。

    相比于之前的getter/setter监控数据变更,Vue 3.0 将会是基于Proxy的变动侦测,通过代理的方式来监控变动,整体性能会得到优化。当我们给某个对象添加了代理之后,就可以改变一些原有的行为,或是通过钩子的方式添加一些处理,用来触发界面更新、其他数据更新等也是可以的。

    对比 Angular,Vue 更加轻量、入门容易。对比 React,Vue 则更专注于模板相关,提供了便利和易读的模板语法,开发者熟练掌握这些语法之后,可快速高效地搭建起前端页面。同时,Vue也在不断地进行自我演化,这些我们也能从 Vue 3.0 的响应式设计、模块化架构、更一致的API 设计等设计中观察到。

    小结

    前端框架很大程度地提升了前端的开发效率,同时提供了各种场景下的解决方案,使得前端开发可以专注于快速拓展功能和业务实现。

    今天我主要介绍了如今比较热门的三个前端框架:Angular/React/Vue。其中,Angular 属于适合大型前端项目的“大而全”的框架,而 React/Vue 则是轻量灵活的库,它们各有各的设计特点。

    对于框架的升级,React 选择了渐进兼容的技术方案,而 Angular/Vue 都曾经历过“断崖式”的版本升级。

    最后,希望你可以思考下:Angular/Vue 在版本升级过程中,为了更彻底地抛弃历史债务而选择了不兼容,这样的做法你们是怎么看待的呢?在我们的业务开发过程中,是否也会存在这样的情况呢?


    # 精选评论

    # **用户7884:

    在学校的时候一直用Vue开发项目,年初实习的时候需要学习react。个人觉得Vue相对来说确实要轻量得多,但是react感觉更规范。比如redux和vuex,vue在业务代码中还是可以直接访问到store甚至进行修改的,只是官方不推荐这么做。但是react不行,更新state还是用新的state去覆盖掉旧的state。

    #     讲师回复:

        嗯,代码规范其实都有两面性,要求严格的地方灵活性和便利性可能会下降,要求松散的地方如果遇上多人开发的情况,由于编码习惯的不一样,代码可能就会很混乱

    去GitHub编辑 (opens new window)
    上次更新: 2022/04/23, 06:56:29
    提升编程体验:组件化与模块化设计
    如何设计合适的状态管理方案

    ← 提升编程体验:组件化与模块化设计 如何设计合适的状态管理方案→

    最近更新
    01
    为什么小程序特立独行
    04-23
    02
    单页应用与前端路由库设计原理
    04-23
    03
    代码构建与 Webpack 必备技能
    04-23
    更多文章>
    Theme by Vdoing | Copyright © 2022-2023 花椒和邻居 | MIT License
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式