Vue现在已经迭代到 3+ 版本,阅读官方文档的过程中发现作者的一些理念和思路很合我口味,很多概念与方案都是基于解决实际问题提出并实现的,且在权衡利弊后勇于打破常规,比如如何看待关注点分离?。可见,Vue 之所以流行,不单单因为作者是国人,更应该是由于 Vue 作为新一代的解决方案提升了前端编程的体验与效率。

本文介绍几个核心概念。

选项式 vs 组合式

Vue 提供两种代码的书写风格——选项式组合式。可简单理解为:前者面向对象编程;后者函数式编程。

选项式:如果你有微信小程序的开发经验,就知道选项式是什么样子,其实就是将组件的逻辑封装到一个对象中,这个对象预定义多个字段和方法(如 data、methods 和 mounted),开发人员需要在适当的地方组织代码。对于有面向对象语言背景的用户来说,这通常与基于类的心智模型更为一致,同时,响应性相关的细节由框架本身处理,对初学者而言更为友好。

组合式:传统的自由无约束的编码风格,顶层就是各个成员变量和 functions,及一些钩子函数。似乎回到了 js 最初的模样,在对象、类、prototype 这些概念普及以前,大多数代码就是一坨变量加一坨 function,然后 onclick 调用。但是 Vue 的组合式风格依托其底层的依赖注入系统,及完善的响应式 API,使得情况不像看上去那么简单,而是呈现出一种螺旋向上的味道,耐人寻味。

官方文档对这两种风格有一些比较,个人比较倾向于组合式,所以本文 Vue 代码都是组合式的。

响应式基础

所谓响应式,就是视图会随着 JS 对象状态的改变而自动改变(也就是MVVM模式),有这种效果的对象就叫作响应式对象(其实就是 JavaScript Proxy)。在组合式 API 中,我们需要显式声明响应式对象,有两种方式——reactive()ref()

reactive()

该 API 返回的对象,是传入对象的代理对象,其所有属性及深层的子属性,都是响应式的。响应式对象的内嵌对象也是响应式对象,就算给它赋值普通对象,如:

const proxy = reactive({})

const raw = {}
proxy.nested = raw // proxy.nested 自动就是响应式对象 console.log(proxy.nested === raw) // false,代理对象和原始对象不是全等的

reactive() 的注意事项和原理

reactive() 有一定的局限:它仅对对象类型有效(对象、数组和 Map、Set 这样的集合类型),而对 string、number 和 boolean 这样的基础类型无效;需要尽量避免对一个响应式变量重新赋值,除非我们有办法将新对象和视图重新建立连接;且当我们将响应式对象的属性(基础类型)赋值或解构至本地变量时,或是将该属性传入一个函数时,我们会失去响应性,如:

let state = reactive({ count: 0 })
// 官方文档这里的表述不是很准确,下面是我的表述:
// 表面上看是重新赋值的 state 状态变化没有引起视图的变化,似乎响应连接丢失了,
// 其实原对象上的响应式连接还在,但是原对象在此处已无法继续访问,所以响应式连接在不在不重要了,
// 重建响应就需要建立视图和新对象的连接。
state = reactive({ count: 1 }) let n = state.count // 基础类型赋值,失去响应性连接
n++ // 不影响 state let { count } = state
count++ // 同上 // state.count 值传递给基础类型形参,也失去响应性连接了
callSomeFunction(state.count)

对于这些情况,有后端经验的同学如果将 reactive() 得到的响应式对象类比成引用类型对象就很好理解,这就是引用类型和值类型在使用过程中需要注意的一些点——变量赋值,如果是引用类型的话,那么指向的是对象的内存地址(新旧对象的内存地址自然是不一样的);如果是值类型,虽然代码看上去都是指向 state.count,其实是拷贝源值到自己的内存块,拷贝完了之后就和源没有关系了。

对于引用类型的“问题”,只要注意点就好了,但是在响应式的场景下,值类型的“拷贝”特性确实让人有点闹心。有没有类似于后端的装箱操作呢?

ref()

该 API 返回的也是响应式对象,它用于将值类型(基础类型)封装成引用类型(对象类型)。也就是说,我们将上一小节代码改造一下,就能保持基础类型数据在各个变量间传递后的响应性,如下:

const state = reactive({
count: ref(0)
}) let n = state.count // 现在 state.count 是引用类型,所以它和 n 指向的是同一个对象
n.value++ // 需要用 value 操作值
// 注意 value 也是响应式的,也就是传递给它的普通对象会自动转为响应式对象,和 reactive() 那边的情况一样
// 同时要注意直接替换掉整个对象会导致出现响应连接丢失的问题(上面提到过)
n.value = { name: 'Tony' }

简言之,我们可以将 ref 对象就看作引用类型对象,就能很快理解它的特性了。唯一要注意的是访问和操作它的值需要 .value,但是在某些时候框架也会帮我们自动解包(不需要使用 .value),可以参看官方文档。

组合式函数

是利用 Vue 的组合式 API 来封装和复用有状态逻辑的函数。说白了,就是业务逻辑封装,它表现形式不是对象,但是有状态,状态作为响应式对象对外暴露使用(如果有的话)。推测是为了和对象形式区分,才称之为组合式函数(就像选项式风格和组合式风格的区别)。

Vue 2 的用户可能会对mixins选项比较熟悉。它也让我们能够把组件逻辑提取到可复用的单元里,然而 mixins 没有自我范围的约束,就像页面里使用<script>引入的 js 文件,容易和其它 js 文件产生命名冲突,对象来源也不清晰,编码时不注意的话也容易产生模块和模块之间隐性的依赖。

其它几个 API

nextTick():DOM 更新是有间隔时间的,在间隔时间内每个组件发生的所有状态改变汇总后一次更新。可以给该函数传递一个回调,在最近的一次 DOM 更新后执行。类似于 HTML5 新增的 window.requestAnimationFrame()

watchEffect(callback):callback 中涉及到的响应式对象状态的变更会触发 callback 执行,如下:

const count = ref(0)
watchEffect(() => console.log(count.value)) // 马上执行一次,-> 输出 0
count.value++ // -> 输出 1

watch():同 watchEffect() 不同在于,watch() 需要显式地给它传递要监听的响应式对象。

构建工具 Vite

伴随 Vue 3 一起出来的还有新的构建工具Vite。下面会简单介绍 Vite 涉及到的关键技术和工具,以及同其它构建工具的比较。

ESM

不同于之前的CJS,AMD,CMD等,ESM是 ECMA 标准化模块系统,也就是说我们可以直接在浏览器中去执行 import,动态引入模块。作为 ECMA 标准,目前 ESM 已经得到 92% 以上浏览器的支持。

ESM 的执行可以分为三个步骤:

  1. 构建: 确定模板依赖关系,下载并将所有的文件解析为模块记录;
  2. 实例化: 将模块记录转换为一个模块实例,为所有的模块分配内存空间,依照导出、导入语句把模块指向对应的内存地址;
  3. 运行:运行代码,填充内存空间。

ESM 使用引用模式指向模块,也就是说如果引用的模块已经存在,那么直接返回模块的内存地址。而 CJS 采用的是拷贝模式,即所有导出模块都是独立的实例。可见前者比后者的效率要高。

基于 ESM,还能做到按需加载模块(碰到 import 再去请求加载文件)。但是我们一般只在开发环境下使用这个特性(不需要每次改动都导致整个 bundle 模块全量打包编译),原因如下段所述。

尽管原生ESM现在得到了广泛支持,但由于嵌套导入会导致额外的网络往返,在生产环境中发布未打包的 ESM 仍然效率低下(即使使用 HTTP/2)。为了在生产环境中获得最佳的加载性能,最好还是将代码预先进行Tree Shaking(移除那些没被使用的代码)、懒加载和 chunk 分割(以获得更好的缓存)。

Rollup

Rollup就是基于 ESM 模块的打包工具,比WebpackBrowserify使用的 CommonJS 模块机制更高效。Rollup 能针对源码进行 Tree Shaking,以及 Scope Hoisting 以减小输出文件大小提升运行性能。

Esbuild

Esbuild提供了与WebpackRollup等工具相似的资源打包能力,但其打包速度却是其他工具的 10~100 倍,原因有二:

  • 大多数前端打包工具都是基于 JavaScript 实现的,边运行边解释。而 Esbuild 则选择使用 Go 语言编写,编译为机器语言,在启动的时候直接执行,性能更高;
  • JavaScript 本质上是一门单线程语言,直到引入Web Worker后才有可能在浏览器、Node 中实现多线程操作,目前大部分打包工具未必有使用 Web Worker 提供的多线程能力。而 GO 则没这方面的“缺陷”,更不用说还有成熟的协程特性。

但是,虽然Esbuild快得惊人,并且已经是一个在构建库方面比较出色的工具,但一些重要功能仍然还在持续开发中——特别是代码分割和 CSS 处理方面(ESM 小节提到的加载性能)。就目前来说,Rollup 在应用打包方面更加成熟和灵活。所以,我们一般在开发时,使用Esbuild进行构建,而在生产环境,则是使用 Rollup 进行打包。

HMR 热更新

Webpack ——重新编译,请求变更后模块的代码,客户端重新加载。

Vite ——请求变更的模块,再重新加载。

Vite 通过chokidar监听文件系统的变更,使相关模块与其临近的 HMR 边界连接失效,只对发生变更的模块重新加载,这样 HMR 更新速度就不会因为应用体积的增加而变慢而 Webpack 还要经历一次打包构建。所以 HMR 场景下,Vite 表现也要好于 Webpack。


关于构建,需要注意的是,如果使用传统 <script src="xxx.js"> 方式引入 Vue 的话,那么就不会涉及到构建步骤,但同时将无法使用单文件组件 (SFC) 语法。传统方式一般用在对现有项目进行局部 Vue 改造的场景下。

Vue 关键概念介绍的更多相关文章

  1. Vue基本概念介绍及vue-cli环境搭建

    1 js中初始化一个Vue对象,传的参数就是对象属性. 挂载点.模板.实例之间的关系. var vm = new Vue({ el:"#app", template:'<di ...

  2. 【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)

    集群概念介绍(一)) 白宁超 2015年7月16日 概述:写下本文档的初衷和动力,来源于上篇的<oracle基本操作手册>.oracle基本操作手册是作者研一假期对oracle基础知识学习 ...

  3. 《AngularJS深度剖析与最佳实践》笔记: 第二章 概念介绍

    第二章 概念介绍 2.1 什么是UI? 用户界面包括内容(静态信息+动态信息), 外观, 交互. 在前端技术栈中分别由HTML, CSS和JS负责. 进一步抽象, 分别对应于MVC三个主要部分: Mo ...

  4. 转载:【Oracle 集群】RAC知识图文详细教程(一)--集群概念介绍

    文章导航 集群概念介绍(一) ORACLE集群概念和原理(二) RAC 工作原理和相关组件(三) 缓存融合技术(四) RAC 特殊问题和实战经验(五) ORACLE 11 G版本2 RAC在LINUX ...

  5. 【转】【Oracle 集群】ORACLE DATABASE 11G RAC 知识图文详细教程之集群概念介绍(一)

    原文地址:http://www.cnblogs.com/baiboy/p/orc1.html 阅读目录 目录 集群概念介绍 什么是集群 为什么搭建数据库集群 数据库集群的分类 可扩展的分布式数据库架构 ...

  6. Docker关键概念阐述

    要了解Docker需要对其体系结构中的几个关键概念有所了解,主要包括image.container.service.swarm.stack等. 在介绍这几个概念时,会使用到一个测试环境,这个测试环境是 ...

  7. 【NS-3学习】ns3-模拟基础:关键概念,日志,命令行参数

    前言 本篇博客先介绍在仿真过程中会使用到的一些关键概念,然后介绍便于调试仿真脚本的常用技术:日志.命令行参数. 关键概念 节点 在因特网术语中,主机(终端)是指任何一台连接到网络的计算设备.ns-3并 ...

  8. Libra教程之:Libra协议的关键概念

    文章目录 Libra协议 交易和状态 交易详解 账本状态详解 版本数据库 账户 账户地址 Proof 验证节点 存储 Libra协议 Libra协议是Libra区块链的基础,本文主要讲解Libra协议 ...

  9. Linux LVM硬盘管理之一:概念介绍

    一.LVM概念介绍: LVM是 Logical Volume Manager(逻辑卷管理)的简写,它由Heinz Mauelshagen在Linux 2.4内核上实现.LVM将一个或多个硬盘的分区在逻 ...

  10. Java SE/ME/EE的概念介绍

    转自 Java SE/ME/EE的概念介绍 多数编程语言都有预选编译好的类库以支持各种特定的功能,在Java中,类库以包(package)的形式提供,不同版本的Java提供不同的包,以面向特定的应用. ...

随机推荐

  1. Springboot自动装配源码及启动原理理解

    Springboot自动装配源码及启动原理理解 springboot版本:2.2.2 传统的Spring框架实现一个Web服务,需要导入各种依赖JAR包,然后编写对应的XML配置文件 等,相较而言,S ...

  2. 【每日一题】【链表&头插法&ASCII码】【链表&迭代器】2022年1月28日-NC1 大数加法

    描述以字符串的形式读入两个数字,编写一个函数计算它们的和,以字符串形式返回. 思路:原生链表&头插法节点值 import java.util.*; public class Solution ...

  3. python软件开发目录规范

    软件开发目录规范 1.文件及目录的名字可以变换 但是思想是不变的 分类管理 2.目录规范主要规定开发程序的过程中针对不同的文件功能需要做不同的分类 myproject项目文件夹 1,bin文件夹 -- ...

  4. python重要内置模块

    目录 包的概念 包的具体使用 编程思想的转变 常用内置模块之collections模块 (收集) 常用内置模块之time模块 (时间) 常用内置模块之random模块 (随机) os模块 sys模块 ...

  5. python 运算优先级

    python 运算优先级,请见下图

  6. echarts map地图中绘制浙江省市区县乡镇多级联动边界下钻的最新geojson数据文件获取和更新

    目录 ECharts Map地图的显示 GeoJSON数据文件获取 在ECharts中绘制浙江省的数据 ECharts Map地图的显示 ECharts支持地理坐标显示,专门提供了一个geo组件,在s ...

  7. 如何使用 IdGen 生成 UID

    在分布式系统中,雪花 ID 是一种常用的唯一 ID 生成算法.它通过结合时间戳.机器码和自增序列来生成 64 位整数 ID,可以保证 ID 的唯一性和顺序性. 在.Net 项目中,我们可以使用 IdG ...

  8. 一文速览 Dubbo 3.0

    本文将带你快速了解 Dubbo3 的设计背景.总体架构与核心特性.与典型用户如阿里巴巴 HSF2 的关系等.也可以通过如下部分了解更多: 小白用户,快速浏览 Dubbo3 核心特性: 下一代通信协议 ...

  9. 15、MySQLTransactionRollbackException: Lock wait timeout exceeded; try restarting transaction

    转载自 一.报错信息: Error updating database. Cause: com.mysql.jdbc.exceptions.jdbc4.MySQLTransactionRollback ...

  10. python进阶之路3之数据类型

    内容概要 pycharm下载与使用 python语法之注释 python语法之变量与常量 python基本数据类型(先大致了解有哪些) pycharm下载与使用 1.该软件分为收费版和免费版 免费版本 ...