Vue 3 学习笔记(一)

Vue 是一种面向数据的编程,在 Vue 应用中定义了数据和模板,Vue 就会自动把数据和模板关联起来,变成 HTML 页面想要展示的效果。

Vue 这种面向数据编程的模式,是参考了 MVVM 这种设计模式。

M 代表 Model,也就是数据。
V 代表 View,视图。对应 Vue 实例中的 template。
VM 代表 ViewModel,视图数据连接层。把数据和模板关联起来,是 Vue 的组件帮我们做的。

下图把 Vue 应用返回的根组件,起名叫做 vm。

Vue 的生命周期函数

生命周期函数:在某一时刻会自动执行的函数。

下面是 coderwhy 老师的视频课程:

Vue 在前端处于什么地位?

主流的 3 大框架:Vue、React、Angular

Angular:入门门槛较高,并且国内市场占有率较低;不否认本身是一个非常优秀的框架

React:在国内外的市场占有率都是非常高的,作为前端工程师也是必须学习的一个框架

Vue:在国内市场占有率是最高的,几乎所有的前端岗位都会对 Vue 有要求

学习哪一门语言更容易找到工作?

找后端的工作:优先推荐 Java、其次推荐 Go、再次推荐 Node

找前端的工作:优先推荐 JS(TypeScript)、其次 Flutter、再次 Android(Java、Kotlin)、iOS(OC、Swift)

在国内找前端工作,优先推荐学习 Vue,其次是 React。

学习 Vue2 还是 Vue3?

尤雨溪:直接学 Vue 3 就行了,基础概念是一样的。

2020 年的 9 月 19 日,Vue3 正式发布,命名为 One Piece。

Vue 3 带来了很多新的特性:更好的性能、更小的包体积、更好的 TypeScript 集成、更优秀的 API 设计。

Vue3 带来的变化(源码)

  • 源码通过 monorepo 的形式来管理代码
  • 源码使用 TypeScript 来进行重写(在 Vue2.x 的时候,Vue 使用 Flow 来进行类型检测)

Vue3 带来的变化(性能)

  • 使用 Proxy 进行数据劫持
  • 删除了一些不必要的 API
  • 编译方面的优化(生成 Block Tree、Slot 编译优化、diff 算法优化)

Vue3 带来的变化(新的 API)

  • 由 Options API 到 Composition API(Options API 包括 data、props、methods、computed、生命周期等等选项)
  • Hooks 函数增加代码的复用性(Vue2.x 通常通过 mixins 在多个组件之间共享逻辑)

如何使用 Vue 呢?

  • 通过 CDN 的方式引入
  • 下载 Vue 的 JS 文件,手动引入
  • 通过 npm 包管理工具安装使用
  • 直接通过 Vue CLI 创建项目,并且使用它

CDN 引入

CDN 称之为内容分发网络(Content Delivery Network 或 Content Distribution Network,缩写 CDN)。

  • CDN 是指通过相互连接的网络系统,利用最靠近每个用户的服务器;
  • 更快、更可靠地将音乐、图片、视频、应用程序及其他文件发送给用户;
  • 来提供高性能、可扩展性及低成本的网络内容传递给用户

常用的 CDN 服务器可以大致分为两种:

  • 自己的 CDN 服务器
  • 开源的 CDN 服务器:国际上使用比较多的是 unpkg、JSDelivr、cdnjs

Vue 的 CDN 引入:

<script src="https://unpkg.com/vue@next"></script>

MVVM 模型

MVC 和 MVVM 都是一种软件的体系结构。

MVC 是 Model-View-Controller 的简称,在前期被使用的架构模式,比如 iOS、前端;

MVVM 是 Model-View-ViewModel 的简称,是目前非常流行的架构模式。

通常情况下,我们也经常称 Vue 是一个 MVVM 的框架。

Vue 官方其实有说明:Vue 虽然并没有完全遵守 MVVM 的模型,但是整个设计是受到它的启发的。

template 属性

HTML 原生提供了 <template> 标签,只不过 template 标签包含的内容,是不会被浏览器渲染的,它里面的内容可以被 js 读取。

template 的两种写法:

  • 使用 script 标签(带有两个属性,一个是 type="x-template",一个是 id)
  • 使用 template 标签(一个 id 属性)

data 属性

data 属性:传入一个函数,并且该函数需要返回一个对象。

在 Vue2.x 中,data 可以传入一个对象(虽然官方推荐是一个函数);
在 Vue3.x 中,必须传入一个函数,否则浏览器就会报错。

data 中返回的对象会被 Vue 的响应式系统劫持,之后对该对象的修改或访问,都会在劫持中被处理。

methods 属性

methods 属性:传入一个对象,在这个对象中可以定义很多方法。

在该方法中,我们可以使用 this 关键字来直接访问到 data 中返回的对象的属性。

官方文档中有这么一段话:

methods 中定义的函数,不能使用箭头函数,理由是箭头函数绑定了父级作用域的上下文,所以 this 将不会按照期望指向组件实例,this.a 将是 undefined。

问题:

  • 为什么不能使用箭头函数?
  • 不使用箭头函数的情况下,this 到底指向的是什么?(可以作为一道面试题)

为什么不能使用箭头函数?

如果使用箭头函数,这个 this 会指向 window,而不是 data 字段中对象返回的属性。

methods: {
    btnClick: () => {
        console.log(this);
    }
}

为什么是 window 呢?

这里涉及箭头函数使用 this 的查找规则,它会在自己的上层作用域中来查找 this;最终刚好找到 script 作用域中的 this,所以就是 window。

箭头函数中的 this 没有做任何的绑定,它会往上层作用域找(一层一层找),最终到达顶层作用域,在 script 标签这一层,找到 window。

this 到底是如何查找和绑定的呢?

其他属性

props、computed、watch、emits、setup 等等,也包含很多的生命周期函数。

Vue3 源码

GitHub 地址:https://github.com/vuejs/core

Vue 的源代码是通过 yarn 进行管理。

终端需要安装 yarn,安装的命令:

npm install yarn -g

从 GitHub 仓库直接下载 Vue3 源码的压缩包,需要在终端中安装一些额外的东西:

进入 Vue3 源码解压后所在的路径,运行命令

yarn install

调试代码的步骤:

勘误:下面的那一行代码,位于 package.json 文件中。

修改之后,运行命令 yarn dev,执行打包操作。

Vue 基础 - 模板语法

VS Code 添加代码片段

生成代码片段的在线工具:https://snippet-generator.app/

使用方法:将写好的一整个 html 代码复制到左侧的窗口,就会自动生成代码片段。

在顶部两个输入框分别输入「代码片段的名称」、「快速插入代码片段的命令」

回到 VS Code,选择 首选项 >> 用户片段,搜索 html.json,将复制的代码片段粘贴到其他的大括号即可。

VS Code 开启自动保存的方法:

模板语法

React 的开发模式(了解):

  • React 使用的是 jsx,所以对应的代码都是编写的类似于 js 的一种语法;
  • 之后通过 Babel 将 jsx 编译成 React.createElement 函数调用。

jsx 语法:将 js 和 html 融合在一起的书写方式

function() {
    return <div></div>    
}

Vue 也支持 jsx 的开发模式:

但多数情况下,使用基于 HTML 的模板语法
在模板中,允许开发者以声明式的方式将 DOM底层组件实例的数据绑定在一起;
在底层的实现中,Vue 将模板编译成虚拟 DOM 渲染函数。

下面是模板语法的例子:

<template>
    <div @click v-bind v-once>
    {{}}
    </div>
</template>

v-bind 的属性绑定

Vue2 template模板中只能有一个根元素,多个元素得包裹到一个div标签里;
Vue3 允许template模板中有多个根元素。

v-bind 绑定 class 有两种方式:

  • 对象语法
  • 数组语法

数组语法,数组中可以包含三元表达式,也可以包含对象。

v-bind 绑定 style 介绍

v-bind:style 绑定 CSS 内联样式。

CSS property 名可以用驼峰式(camelCase)或短横线分隔(kebab-case)来命名。

绑定 class 有两种方式:

  • 对象语法
  • 数组语法

v-bind 动态绑定属性:绑定的属性名是不确定的,即用户自定义的属性名。

写法:

<div :[PropertyName]="PropertyValue">
</div>

<script>
data() {
    PropertyName: "自定义的属性名"
    PropertyValue: "属性值"
}
</script>

v-bind 直接绑定一个对象:将一个对象的所有属性,绑定到 html 元素上的所有属性。

v-on 绑定事件

前端开发中,需要经常和用户进行各种各样的交互,这时就必须监听用户发生的事件,比如:

  • 点击
  • 拖拽
  • 键盘事件等

在 Vue 中如何监听事件呢?使用 v-on 指令。

v-on 可以绑定单个事件(单个函数),也可以绑定内联表达式(inline statement)和对象。

v-on 的参数传递

v-on 绑定的函数需要传入多个对象的时候,event 对象前面要加多一个美元符号 $

v-on 的修饰符

条件渲染

Vue 提供了下面的指令来进行条件判断:

  • v-if
  • v-else
  • v-else-if
  • v-show

v-if 和 template 元素结合使用

如果我们希望批量显示/隐藏多个元素,一般是在 template 中使用 div 元素进行包裹,但这样用于包裹的 div 最终会被渲染出来,增加了一个额外的 div 标签。

<template>
    <div v-if="isShow">
        <h2>哈哈哈哈</h2>
        <h2>哈哈哈哈</h2>
        <h2>哈哈哈哈</h2>
    </div>
    
    <div v-else>
        <h2>呵呵呵呵</h2>
        <h2>呵呵呵呵</h2>
        <h2>呵呵呵呵</h2>
    </div>
</template>

template 元素中可以嵌套 template 元素,它可以当做不可见的包裹元素,并且添加 v-if 指令,但是最终 template 不会被渲染出来,它有点类似于小程序中的 block。

<template>
    <template v-if="isShow">
        <h2>哈哈哈哈</h2>
        <h2>哈哈哈哈</h2>
        <h2>哈哈哈哈</h2>
    </template>
    
    <template v-else>
        <h2>呵呵呵呵</h2>
        <h2>呵呵呵呵</h2>
        <h2>呵呵呵呵</h2>
    </template>
</template>

v-show 和 v-if 的区别

用法上的区别:

  • v-show 不支持 template;
  • v-show 不可以和 v-else 一起使用

本质上的区别:

  • v-show 元素无论是否需要显示到浏览器上,它的 DOM 实际都是有渲染的,只是通过 CSS 的 display 属性来进行切换;
  • v-if 为 false 时,其对应的元素压根不会被渲染到 DOM 中。

开发中如何进行选择呢?

  • 如果我们的元素需要在显示和隐藏之间频繁切换,那么使用 v-show(通过 CSS 来实现显示和隐藏间的切换,不会那么耗费性能);
  • 如果不会频繁地发生切换,那么使用 v-if。

v-for 列表渲染

v-for 支持遍历数组、对象和数字。

v-for 遍历对象,支持有一二三个参数:

  • 一个参数:value in object
  • 二个参数:(value, key) in object
  • 三个参数:(value, key, index) in object

v-for 中的 key 是什么?

认识 VNode

VNode 的全称是 Virtual Node,也就是虚拟节点。

事实上,无论是组件还是元素,它们最终在 Vue 中表示出来的都是一个个 VNode;

VNode 的本质是一个 JS 对象;

虚拟 DOM

虚拟 DOM:多个 VNode 形成的树结构

虚拟 DOM 最大的优点在于,可以做跨平台,可以在服务端渲染,可以做移动端

Vue 中对于列表的更新是如何操作的呢?

Vue 对于有 key 和没有 key 会调用两个不同的方法:

有 key,就使用 patchKeyedChildren 方法;
没有 key,那么就使用 patchUnkeyedChildren 方法。

Vue 的 diff 算法

diff 算法用来比较新旧两个 VNode 列表。

比较两个新旧节点的类型 type 和 key 是否相同。

type 和 key 都一样的话,节点是不需要进行更新的。

如果新节点比旧节点多,会挂载(mount)新的节点。
如果旧节点比新节点多,会卸载/销毁(unmount)旧的、多余的节点。

最后一步:新旧节点的排序比较混乱,尽可能地移动节点,移除新节点中没有的旧节点,新增旧节点中没有的新节点。

尽可能在旧的节点列表里面,找到新的列表中对应的节点,比如新的节点里面有一个 h,就会尽可能地在旧节点里面找到 h。

patch:

如果 n1 非空,就进行更新操作;
如果 n1 为 null,就进行挂载操作。

computed 计算属性

对 data 中的数据进行复杂处理(对数据进行运算后再在模板中使用的情况),会用到计算属性。

复杂 data 的处理方式

计算属性的用法:

  • 选项:computed
  • 类型:{[key: string]: Function | {get: Function, set: Function}}

key 的值是一个函数或者对象。

插值语法(Mustache语法)的 3 个缺点:

  • 模板中存在大量的复杂逻辑,不便于维护(模板中使用表达式的初衷是用于简单的计算);
  • 当有多次一样的逻辑时,存在重复的代码;
  • 多次使用的时候,很多运算需要多次执行,没有缓存

计算属性的缓存:

  • 计算属性会基于它们的依赖关系进行缓存
  • 在数据不发生变化时,计算属性是不需要重新计算的
  • 但如果依赖的数据发生变化,在使用时,计算属性依然会重新进行计算

计算属性的 setter 和 getter 方法

计算属性的完整写法,应该是包含 setter 和 getter 方法的,需要写成对象的形式,但我们一般只用到 getter 方法,所以就会写成函数的形式,这个函数就是 getter 方法。

老师上课的笔记:计算属性在大多数情况下,只需要一个 getter 方法即可,所以我们会将计算属性直接写成一个函数

下面是计算属性的完整写法:

给计算属性传参(赋值)的时候,会调用计算属性的 setter 方法。

computed: {
    fullName: {
        set: function(newValue) {
            console.log(newValue);
        },
        get: function() {
            return this.firstName + " " + this.lastName
        }
    }
}

下面是计算属性的简略写法(只用到 getter 方法):

computed: {
    fullName() {
        return this.firstName + " " + this.lastName      
    }
}

认识侦听器 watch

在某些情况下,我们希望在代码逻辑中监听某个数据的变化,这个时候就需要用侦听器 watch 来完成了。

侦听器的用法:

  • 选项:watch
  • 类型:{[key:string]: string|Function|Object|Array}

watch 后面跟一个对象,对象里面是一个键值对,值可以是函数,也可以是字符串/对象/数组。

侦听器的使用场景:侦听当 data 里面的数据发生变化时,想要进行一些逻辑处理(JavaScript,例如向服务器发送网络请求)

watch 是 Vue 实例中的一个 option。

watch 中的 question 侦听 data 中的属性的名称。

watch 可以调用 methods 中定义的函数。

上图中的 你问题${this.question}的回答是 哈哈哈哈哈哈 是 ES6 中的模板字符串语法,可以很方便地进行字符串的拼接。

字符串拼接表达式的时候,可以使用 ${} 来拼接。

侦听器的配置选项

  • 深度侦听
  • 立即执行

下图的 info 是一个来自 data 中的对象,当对象里的一个键值对发生变化、而非整个对象发生变化时,watch 在默认情况下,无法监听到内部属性的变化。

此时就需要给 watch 配置一个 deep 选项,实现深度侦听。

侦听器 watch 的其他方式

侦听器的其他写法:

使用 $watch 的 API,需要配合生命周期函数一起使用

生命周期函数与实例中的 data()、methods 选项处于同一层级。

浅拷贝和深拷贝

浅拷贝在堆内存中的表现:

浅拷贝只会拷贝第一层,如果内部嵌套了对象,还是会指向对象原来的内存地址。

那如果要实现深拷贝,该怎么办呢?

需要借助 JSON 的两个方法:

  • stringify():先把对象转换成字符串
  • parse():转成字符串之后,再对字符串进行还原,就会在内存中生成一个新的对象
const info = { name: 'phh', age: 28, friend: {name: 'angola peng'} };   

const obj = JSON.parse(JSON.stringify(info));

经过上面两步,就可以实现深拷贝,生成一个新的对象 obj。

Vue3 的表单

v-model 本质上是语法糖。

双向绑定:可以将 data 中的值绑定到 input(表单)的 value 属性上面,同时当 input 的 value 发生变化时,会将最新的值更新到 data 中。

v-model 修饰符 lazy

修饰符 lazy 的本质:将 v-model 内部绑定的 input 事件切换为 change 事件,只有在提交(比如按下回车键)时才会触发。

v-model 修饰符 number:将输入的文本转换为数字类型
v-model 修饰符 trim:去除用户输入的 value 前后的空格

Vue 组件化开发

组件化开发的思想:将页面拆分成一个个小的功能块,之后像搭积木一样,把组件组装到一起。

现在整个大前端开发都是组件化的天下,无论是三大框架,还是跨平台方案的 Flutter,甚至是移动端都在转向组件化开发,包括小程序的开发也是采用组件化开发的思想。

注册组件的方式

注册组件分成两种:

  • 全局组件:在任何其他的组件中都可以使用的组件
  • 局部组件:只有在注册的组件中才能使用的组件

注册全局组件

全局组件:注册的组件可以在任何的组件模板中使用。

const app = Vue.createApp(App);

// 伪代码
// app.component("组件名称", 组件对象)

app.component("component-a", {
    template: '#component-a',
    data() {
        return {
            message: 'hello world'
        }
    }
})

实际开发中一般不推荐使用全局组件。

全局组件的逻辑

组件本身也可以有自己的代码逻辑:比如 data、computed、methods 等。

组件的名称

通过 app.component 注册组件时,第一个参数是组件的名称,定义组件名的方式有两种:

方式一:使用 kebab-case(短横线分隔符)

app.component('my-component-name', {
    /* … */
})

在引用这个组件时,也必须使用 kebab-case,例如 <my-component-name>

方式二:使用 PascalCase(驼峰标识符)

app.component('ComponentName', {
    /* … */
})

组件命名一般采用大驼峰,首字母必须是大写。

采用驼峰命名的方式,在模板中引用这个组件时,有两种写法:

  • <component-name>
  • <ComponentName>(实际开发中不推荐使用这种)

注册局部组件

局部组件:通过 components 属性选项来注册,这个选项与之前的 data、methods、computed 处于同一层级。

Vue 的开发模式

单文件组件的后缀为 .vue,浏览器无法识别,因此还需要经过构建、打包的环节,借助 Webpack 或 Vite 将 .vue 转换为普通的 js 文件。

<template>

</template>

<script>

</script>

<style>

</style>

单文件的特点

如何支持单文件组件

想要使用这一单文件组件的 .vue 文件,比较常见的两种方式:

  • 方式一:使用 Vue CLI 来创建项目,项目会默认帮我们配置好所有的配置选项,可以在其中直接使用 .vue 文件(脚手架本质上也是基于 Webpack 的
  • 方式二:自己使用 Webpack 或 rollup 或 Vite 这类打包工具,对其进行打包处理

最终,无论是后期做项目,还是在公司进行开发,通常都会采用 Vue CLI 的方式来完成。

在学习阶段,为了更好地理解 Vue CLI 打包项目的过程,会先讲解一部分 Webpack 的知识。

Webpack

Webpack 的官方文档:https://webpack.js.org
Webpack 的中文官方文档:https://webpack.docschina.org

Webpack 依赖 Node 环境,因此需要先安装 nodejs。

下面的内容基于最新的 Webpack 5。

认识 Webpack

三大框架的脚手架都是基于 Webpack 的。

Webpack is a static module bundler for modern JavaScript applications.

Webpack 是一个静态的模块化打包工具。

Webpack 默认支持各种模块化开发,包含 ES Module、CommonJS、AMD 等。

Vue 项目加载的文件有哪些?

Webpack 会对哪些文件进行打包?

Webpack 的安装

Webpack 的安装目前分为两个:webpackwebpack-cli

当我们在命令行中使用 webpack,并且传入一些参数时,就需要用到 webpack-cli

webpack 在执行时是依赖 webpack-cli 的,如果没有安装就会报错。

webpack 和 webpack-cli 的关系:

全局安装 webpack 和 webpack-cli:

npm install webpack webpack-cli -g

老师视频中使用的版本:

webpack 5.37.1
webpack-cli 4.7.0

在实际开发中,很少会用全局的 webpack 来打包项目,一般都会针对项目单独安装一个 webpack 版本,这就需要用到局部 webpack。

项目中会包含多个包(package),使用 package.json 来管理这些包。

生成 package.json 文件的方法:

打开终端,输入 npm init,按下回车,需要给包起一个名字,接下去一路回车,就会生成 package.json 文件。

这种生成 package.json 文件的方法,适用于文件包含有中文名称的情况。

另外一个生成 package.json 文件的方式:

npm init -y

-y 表示后面所有的选项都是 yes,可以快速创建 package.json 文件。

局部安装的 Webpack 分为两类:

  • 开发阶段依赖的 Webpack:开发者开发时使用的
  • 生产阶段依赖的 Webpack:项目正式上线后,面向用户的

在项目文件夹中直接运行 npm install webpack webpack-cli,默认安装生产依赖的 webpack(面向用户的)。

开发时依赖的 Webpack 的安装命令:

npm install webpack webpack-cli --save-dev

上面的安装命令也可以简写成:

npm install webpack webpack-cli -D

运行局部 Webpack 的命令:

npx webpack

Webpack 对项目进行打包,默认会去找 src 文件夹下面的 index.js 文件,这被称为入口文件

如果把 index.js 文件命名为其他的名字,例如 main.js,在运行打包命令的时候,要带上额外的参数,配置 main.js为入口文件:

npx webpack --entry ./src/main.js

webpack 配置文件

项目文件中单独创建一个 webpack.config.js文件,来对 webpack 进行配置:

出口 output 的 path 属性,它的值应该为绝对路径,即完整的文件路径。

但这个绝对路径比较长,我们通常会用 Node 中的 path 模块,来简化绝对路径的写法。

调用 path 模块的 resolve 方法,对两个路径进行拼接:

path.resolve(__dirname, "./build")

__dirnamewebpack.config.js 文件所在的绝对路径,./build是打包后的文件存放位置的相对路径。

将这两个路径拼接后,就可以找到打包文件所在的位置啦。

配置文件的名称多数情况下为 webpack.config.js,如果你执意要更改文件的名称,例如更改为 why.config.js,需要在 package.json中作相应的调整,build 属性后面需要配置额外的参数:

"build": "webpack --config why.config.js"

package.json 文件中配置好 webpack 和 webpack-cli 的依赖后,在终端输入 npm install,安装的就是局部的 webpack。

Webpack 打包的依赖关系图

css-loader 的使用

Webpack 需要 loader 来加载 CSS 文件。

安装 css-loader:

npm install css-loader -D

loader 是什么呢?

  • loader 可以用于对模块的源代码进行转换
  • 我们可以将 css 文件也看成是一个模块,我们是通过 import 来加载这个 css 模块的
  • 在加载这个 css 模块时,Webpack 其实并不知道如何对其进行加载,必须制定对应的 loader 来完成这个功能

loader 的配置

css-loader 加载 css 文件的 3 种方式:

  • 内联方式
  • CLI 方式(Webpack5 中不再使用)
  • 配置方式(多采用这一种)

内联方式:使用较少,因为不方便管理。

在引入的 css 样式前加上使用的 loader,并且使用英文感叹号分割:

import "css-loader!../css/style.css"

loader 的配置方式:

webpack.js.config 中写明我们用到的 loader。

认识 style-loader

前面的 css-loader 只负责将 .css 文件进行解析,并不会将解析之后的 css 插入到页面中。

如果想要把完成插入 style 的操作(将解析后的 css 插入到 html 中的 style 标签),还需要用到另外一个 loader——style-loader

安装 style-loader:

npm install style-loader -D

两个 loader 的执行顺序:先执行 css-loader,再执行 style-loader。

但在 use 数组中,它是从后往前执行 loader 的,因此先执行的 css-loader 要写在后面。

如何处理 less 文件

less 文件要先转换为普通的 CSS 文件,转换需要用到一个工具 less compiler,简称 lessc。

lessc 与 Webpack 没有任何关系,它是一个独立的工具。

局部安装 lessc:

npm install less -D

将 lessc 工具与 Webpack 关联起来,需要安装 less-loader。实际上 less-loader 依赖 lessc 工具。

局部安装 less-loader:

npm install less-loader -D

之后同样需要在 webpack.config.js 文件中进行配置,处理 less 文件需要用到 3 个 loader:

认识 PostCSS 工具

PostCSS 是一个通过 JS 来转换 css 样式的工具;
这个工具可以帮助我们进行一些 CSS 的转换和适配,比如自动添加**浏览器前缀(user-select)**、css 样式的重置;
但是实现这些功能,我们需要借助 PostCSS 对应的插件。

使用 PostCSS 的两个步骤:

  1. 查找 PostCSS 在构建工具中的扩展,比如 webpack 中的 postcss-loader
  2. 选择可以添加你需要的 PostCSS 相关的插件,例如 autoprefixer 插件

PostCSS 工具与 Webpack 无关,可以独立使用。

安装 PostCSS:

npm install postcss postcss-cli -D

安装 autoprefixer 插件:

npm install autoprefixer -D

在 Webpack 中使用 PostCSS,还需要安装 postcss-loader:

npm install postcss-loader -D

webpack.config.js 中配置 postcss-loader 时,需要额外配置使用到的 autoprefixer 插件:

另外一个 postcss 插件:postcss-preset-env

现代的 CSS 特性:最新的 CSS 中表示颜色的十六进制可以有 8 位,例如 #12345678,而不是只能有 6 位——#123456。

局部安装插件:

npm install postcss-preset-env -D

Webpack 打包其他资源

file-loader

加载图片这种资源,需要使用 file-loader。

引用图片的两种方式:

  • 一种是给 div 设置图片背景,在 CSS 中使用 background-image: url()
  • 一种是在 img 元素的 src 属性引入图片

在 CSS 中使用 background-image: url() 的配置方式:

Webpack5 中使用 file-loader 打包图片,需要添加额外的配置:

  • options 中关闭 esModule 模块(file-loader 默认使用 ES6 模块解析),启用 file-loader 的 CommonJS 模块(不配置这个,html 文件中图片路径不对)
  • 后面要加多一个 type,否则会打包生成两张图片

下图来自 Stackoverflow

在 img 元素的 src 属性引入图片的配置方法:

在 template 中引用图片,图片会通过 vue-loader 进行编译。

图片打包后生成文件的命名


上图有个错别字,截图 ▶ 截取。

file-loader 打包后的图片会放在 build(dist) 文件夹中,如果想把打包后的文件都归集到 img 文件中,需要给 file-loader 配置 outputPath 字段。

对打包后的图片进行命名

实际开发中,可以把 outputPath 去掉,与 name 字段进行合并,同时指定保存的文件夹和打包生成的文件名。

url-loader

url-loader 和 file-loader 的工作方式是相似的,但是可以将较小的文件,转成 base64 的 URI。

服务器「高并发」,经常会对小的图片进行处理,减少浏览器向服务器发送请求的次数:

  • 精灵图:将多张小的图片合并为一张图
  • 字体图标:iconfont,矢量图
  • 对小的图片进行编码:编码成 base64 的 URI

安装 url-loader:

npm install url-loader -D

url-loader 和 file-loader 的区别:

  • 想把全部图片打包,就用 file-loader
  • 想把部分小图片使用 base64 编码,就用 url-loader

url-loader 配置时,需要增加一个字段 limit,划定使用 base64 编码的界限。

如下图,当图片小于 100kb 时,使用 base64 进行编码。

认识 asset module type

在 Webpack5 之前,加载资源需要使用一些 loader,比如 raw-loader、url-loader、file-loader

从 Webpack5 开始,我们可以直接使用**资源模块类型(asset module type)**,来替代上面的这些 loader。

使用 asset module type,不需要安装任何模块,可以直接使用。

type: "asset" 可以像之前的 url-loader 一样,对小于 100kb 的图片使用 base64 编码,不过它的配置方式比较特殊:

配置打包后的路径和文件名,使用字段 generator,需要注意的是,filename 中的扩展名 [ext] 前面不需要加英文句号

加载字体文件

在 src 文件中放入字体文件夹 font,里面包含 3 个字体文件和 2 个 css 文件。

接着在 element.js 中导入 iconfont.css 文件。

加载字体文件有两种方式:

  • 使用 file-loader 模块(参考加载图片的写法)
  • 使用 Webpack5 内置的资源模块类型(asset module type)

webpack.config.js 中添加配置:

Webpack 插件

Webpack 的另一个核心是 Plugin。

CleanWebpackPlugin

前面每次修改了一些配置,重新打包时,都需要手动删除 build 文件夹。

我们可以借助一个插件 CleanWebpackPlugin,来帮我们自动删除之前打包的文件。

安装插件:

npm install clean-webpack-plugin -D

安装插件之后,还需要在 webpack.config.js 中导入:

const { CleanWebpackPlugin } = require("clean-webpack-plugin");

上面的 CleanWebpackPlugin 是一个类。

导入后,在 module.exports 中使用插件:

每次运行 npm run build 时,插件会自动删除之前打包生成的 build 文件夹,生成新的 build。

HtmlWebpackPlugin

之前写的代码,html 文件都是没有打包到 build 文件夹中,如果要对放在根目录下的 html 进行打包,需要用到另外一个插件——HtmlWebpackPlugin。

配置 HtmlWebpackPlugin 插件:

配置好插件后,我们可以把之前放在根目录下的 index.html 文件删除,因为这个插件会在打包后的 build 文件中生成一个 index.html

自动生成的 index.html 文件,是根据 ejs 的一个模板来生成的。

自定义 HTML 模板

可以在根目录下创建一个 public 文件夹,存放自定义的 html 模板。

webpack.config.js 的 HtmlWebpackPlugin 插件传入我们自定义的 html 模板:

之后 Webpack 进行打包时,就会以自定义的 html 模板为基础进行打包,而不是使用 HtmlWebpackPlugin 插件的 ejs 模板。

CopyWebpackPlugin

放在 public 文件夹下的文件,例如 favicon.ico,一般在打包时会直接复制到 build 文件夹中。

要想实现自动复制(文件拷贝),需要借助插件 CopyWebpackPlugin。

注意:直接使用最新版的 CopyWebpackPlugin 插件,打包时会报错,建议使用视频中 coderwhy 老师用到的 9.0.0 版本。

安装 CopyWebpackPlugin 插件:

npm install copy-webpack-plugin@9.0.0 -D

插件配置:

webpack.config.js 中引入插件:

const CopyWebpackPlugin = require('copy-webpack-plugin');

接着在下面的插件中 new 一个插件:

favicon.ico 从 public 复制到 build 文件夹,并忽略 public 文件夹中所有叫 index.html 的文件。

Webpack 的 Mode 配置

不设置的话,默认是生产模式。

当我们把 mode 设置为 development(开发模式),Webpack 会自动配置下面一大堆选项。

Babel

Webpack 打包 js 和 .vue 文件。

Babel 是一个工具链,可以将 ES6+ 的代码转换为向后兼容的 JS。

安装 babel:

npm install @babel/core @babel/cli -D

一个小例子:将使用 ES6 语法写的 demo.js 转换成 ES5 语法

局部安装的 babel,在终端调用的时候,要用 npx 命令:

npx babel demo.js --out-dir dist 

--out-dir 指定文件输出的目录,输出到 dist 文件夹。

如果想直接指定输出的文件名,可以改写一下命令:

npx babel demo.js --out-file test.js

这样输出的 test.js 文件就和 demo.js 在同一个路径下。

运行 babel 时,转换箭头函数,就需要配置用到的插件:

npx babel demo.js --out-file test.js --plugins=@babel/plugin-transform-arrow-functions

babel 转换 ES6 块级作用域插件:transform-block-scoping

ES6 块级作用域,指的是 ES6 中的关键字 const、let。

安装插件:

npm install @babel/plugin-transform-block-scoping -D

在命令行中同时使用两个插件的写法,使用逗号进行分割:

npx babel demo.js --out-file test.js --plugins=@babel/plugin-transform-arrow-functions,@babel/plugin-transform-block-scoping

Babel 的预设 preset

安装 Babel 预设:

npm install @babel/preset-env -D

使用预设:

npx babel demo.js --out-file test.js --presets=@babel/preset-env 

上面的命令就可以取代前面长长的命令啦,也不需要去安装两个插件,来分别实现 箭头函数、ES6 块级作用域 的转换。

Babel 编译器执行原理(了解)

Babel-loader

Webpack 对代码进行打包时,并不会将使用 ES6 编写的代码转换成 ES5,这样会存在一个问题:

如果用户使用的浏览器不支持 ES6 语法,就会导致无法正常使用我们的服务。

babel-loader:将 Babel 和 Webpack 结合起来,加载所有 js 结尾的文件,如果文件中含有 ES6 语法编写的代码,就会将 ES6 语法转换成 ES5。

安装 babel-loader:

npm install babel-loader -D

如果前面没有安装过 @babel/core,在安装 babel-loader 时也要同时安装:

npm install babel-loader @babel/core -D

安装之后,在 webpack.config.js 中添加配置:

因为用到的插件比较多,我们还可以使用 babel 预设,替代上面的插件:

Babel 的配置文件

像之前学习的 postcss 一样,我们可以把放在 webpack.config.js 中的 babel 配置信息,放到一个单独的文件中。

更常见的是使用第一种配置文件,创建 babel.config.js文件。

在根目录下创建 babel.config.js文件:

接着在 webpack.config.js 调整一下之前的配置,可以写得很简洁了:

Vue 源码的打包

首先要安装 Vue,在命令行中运行:

npm install vue

这一次不需要在 vue 后面加上 -D 的参数,因为 Vue 不仅是在开发时需要用到,项目正式上线的时候,也会用到 Vue。

Vue 有多个版本,大致分为两个:

  • runtime + compiler(后面的 compiler 可以用来解析 Vue 代码中的 template 模板)
  • runtime-only(这个版本的体积比较小,不支持对 template 模板进行解析)

直接使用 import { createApp } from 'vue' 引入的 Vue 是 runtime-only 的版本,无法解析 template 模板,最终在 html 页面无法显示出来。

Vue 和 Webpack 一起使用,引入时需要使用包含 compiler 的 Vue 版本:

import { createApp } from 'vue/dist/vue.esm-bundler'

Vue 开发过程中,有 3 种方式来编写 DOM 元素:

  • template 模板的方式(之前一直在用的方式)
  • render 函数的方式
  • 通过 .vue 文件中的 template 元素来编写模板

方式一需要通过 Vue 源码中的 compiler 来对 template 模板进行解析;
方式三需要用到 vue-loader。

VSCode 对 SFC 文件的支持

SFC:单文件组件

VS Code 中两个和 SFC 相关的插件:

  • Vetur
  • Volar,Vue 官方推荐的插件

Vue-loader

安装 Vue-loader,同样是开发时依赖:

npm install vue-loader -D

配置 Vue-loader:

webpack.config.js 中配置好规则,还需要引入一个 VueLoaderPlugin 插件。

控制台会有一个警告信息:

__VUE_OPTIONS_API__:是对 Vue2 进行适配的(Vue2 中会使用 Options,Vue3 开始就去掉了 Options)

__VUE_PROD_DEVTOOLS__:在生产环境是否使用 Vue 调试工具。

要去除这个警告信息,可以在 webpack.config.js 的 DefinePlugin 插件中进行配置。

配置之后,重新进行打包,控制台就不会出现这个警告信息了。

安装 Vue-loader 之后,内部有一个 @vue/compiler-sfc 模块,会对 template 模板进行解析,这时就不需要用到 Vue 源码中的 compiler。

因此,我们可以把最开始使用的 runtime + compiler vue.esm-bundler 版本,切换为 只包含 runtime 的 vue 版本。

Webpack 搭建本地服务器

第一种开启 watch 的方式:在 webpack.config.js 的导出配置中,添加 watch: true

在真实开发中,上面这种 watch 的配置方式,其实用得还是比较少。

webpack-dev-server

上面的方式可以监听到文件的变化,但是事实上它本身是没有自动刷新浏览器的功能的:

因为我们是借助 VS Code 中的 live-server 插件来完成自动刷新浏览器的;

但是,我们希望在不使用 live-server 插件的情况下,可以具备 live reloading(实时重新加载) 的功能。

安装 webpack-dev-server:

npm install webpack-dev-server -D

安装之后,在 package.json 文件的脚本字段,添加配置 "serve": "webpack serve"

serve通过 webpack-cli 来帮助我们做解析的。

webpack-dev-serve 在编译之后不会写入到任何输出文件,而是将 bundle 文件保留在电脑内存中。webpack-dev-serve 使用了一个 memfs 库。

接着在终端运行 npm run serve,就能看到 Webpack 帮我们搭建的本地服务。

这个本地服务是基于 Node 的 express 框架搭建的本地服务器。

如果有些资源没有从 Webpack 打包的文件中加载到,就会从 devServer 的 static 中配置的文件夹寻找。

浏览器向 express 服务器请求某个资源的时候,如果在打包后的资源中找不到,express 就会去 devServer 的 static 中配置的文件夹寻找。

视频中老师用到的 contentBase 字段已经被弃用了。

在真实开发中,如果希望有些资源在开发阶段暂时不要复制到打包的文件中,加快打包速度,等到上线(生产)阶段再作复制,就需要给 devServer 的 static 配置路径。

开发阶段:devServe 的 static 配置文件夹路径 ./public
打包(生产)阶段:使用 CopyWebpackPlugin

认识模块热替换(HMR)

HMR 是 Hot Module Replacement,翻译为模块热替换。

模块热替换是指在应用程序运行过程中,替换、添加、删除模块,而无需重新刷新整个页面

在 Webpack 中,一个文件就可以看成是一个模块。

开启 HMR

修改 webpack 的配置:

修改 webpack 配置后,修改文件后依然会刷新整个页面,这是因为我们要去指定哪些模块发生更新时,进行 HMR:

增加 if 判断,当 module.hot 为 true,进行模块热替换。

开启模块热更新后,浏览器控制台会有下图的提示。

指定模块发生更新进行 HMR 后,当我们修改文件,控制台会打印下面的信息:

Vue 框架的 HMR

HMR 的原理

Socket 长连接 -> 即时通信(微信、直播间评论区、送礼物、进场消息)
长连接:当服务器数据发生变化时,可以主动地向客户端发送请求

Http 链接 -> 短连接
客户端发送 http 请求 -> 和服务器建立连接 -> 服务器做出响应 -> 断开连接

HMR 原理图

devServer 的其他配置

Proxy

Webpack 的 resolve 模块解析配置

resolve 用于设置模块如何被解析:

  • 开发中我们会有各种各样的模块依赖,这些模块可能来自于自己编写的代码,也可能来自第三方库;
  • resolve 可以帮助 webpack 从每个 require/import 语句中,找到需要引入的模块代码;
  • webpack 使用 enhanced-resolve 库来解析文件路径。

webpack 能解析三种文件路径:

  • 绝对路径
  • 相对路径
  • 模块路径:在 resolve.modules 中指定的所有目录检索模块,默认值是 [‘node_modules’],所以默认会从 node_modules 中查找文件;

如何确定导入的是一个文件还是文件夹?

import { sum } from "./js/math";

如何确定导入的 math 是一个文件还是文件夹呢?

如果是一个文件:

  • 如果文件具有扩展名,则直接打包文件;
  • 否则,将使用 resolve.extensions 选项作为文件扩展名解析;

resolve.extensions 默认配置了一些扩展名,遇到没有后缀的文件,就会依次为文件加上后缀,去匹配文件夹中是否有对应的文件。

resolve: {
    extensions: [".js", ".json", ".mjs", ".wasm"]     
}

如果是一个文件夹,会在文件夹中根据 resolve.mainFiles 配置选项中指定的文件顺序查找:

  • resolve.mainFiles 的默认值是 [“index”];
  • 再根据 resolve.extensions 来解析扩展名;

添加额外的扩展名,例如在配置中添加 .vue,之后从 App.vue 导入模块时,文件末尾就不需要带上 .vue 后缀了。

alias:可以为路径起别名,简化导入模块时路径的书写。

开发环境和生产环境分离

之前我们把所有配置都写在了一个 webpack.config.js 中,里面混合了开发环境和生产环境的配置。

接下来我们要对其进行分离,将配置写到两个不同的文件中。

在根目录下创建一个 config 的文件夹,创建 3 个配置文件,一个公共的配置文件(comm),一个是开发环境的配置文件(dev),一个是生产环境的配置文件(prod)。

接着修改 package.json 中的配置信息:

给脚本的 build 和 serve 字段后面添加额外的配置,这样我们在终端中运行 npm run buildnpm run serve 才不会报错。

"build": "webpack --config ./config/webpack.prod.config.js",    
"serve": "webpack serve --config ./config/webpack.dev.config.js"

Vue CLI 脚手架

Vue CLI 本质上是一个工具,在使用之前需要先安装。

Vue CLI 安装和使用

全局安装 Vue CLI:

npm install @vue/cli@4.5.13 -g

老师视频中安装的是 4.5.13 版本的脚手架,这里也安装相同的版本。

安装好之后,通过 vue 的命令来创建项目:

vue create 项目的名称   
```   

在 Mac 电脑上使用 `vue create 项目的名称` 创建项目可能会报错,报错信息:  

```       

gyp: No Xcode or CLT version detected!   
```     

解决方法:   

在终端分别输入两行命令  

```   

sudo rm -rf $(xcode-select -print-path)   
sudo xcode-select --install     
```       

之后重新运行创建 vue 项目的命令,覆盖之前创建的东西。   


用 Vue 脚手架创建的 demo 项目的目录,结构和前面学习的 Webpack 非常像:      

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16487356417880.jpg)


`.browserslistrc` 文件:设置适配浏览器的范围


## 认识 Vite

Vite 是尤雨溪写的构建工具。   

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16489530669622.jpg)

### Vite 的构造   

Vite 由两部分组成:   

* 一个开发服务器,它基于原生 ES 模块,提供了丰富的内建功能,HMR(模块热替换)的速度非常快速   
* 在构建阶段,它是一套构建指令,使用 rollup 打开我们的代码,并且它是预配置的,可以输出生产环境的优化过的静态资源    

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16489541314670.jpg)

版本比较新的浏览器支持 ES Module(**原生浏览器也是支持 ES Module 的**),不需要使用构建工具,也可以识别 ES Module 的代码。    

在 `index.html` 中引入包含 ES Module 的 js 文件,需要给 script 标签增加一个属性 type。  

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16489580248866.jpg)


但这也存在一些弊端:  

* 浏览器无法识别某些文件,例如 ts、vue、less、scss    
* 如果用到的包,之间的依赖太多,会发送过多的网络请求    

因为存在这些弊端,我们还是需要使用构建工具。这两个弊端,就是 Vite 想帮我们解决的。   

### Vite 的安装和使用  

Vite 本身也是依赖 Node 的,所以也需要安装好 Node 环境。  

Vite 要求 Node 版本是大于 12 版本的。  

全局安装与局部安装 Vite:  

npm install vite -g
npm install vite -D


### Vite 对 css、less 和 postcss 的处理   

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16489652801181.jpg)

Vite 对 css 的处理,不像 webpack 需要安装 css-loader、style-loader,内部的代码可以直接对 css 进行处理。   

Vite 对 less 的处理,需要安装 less 工具:  

npm install less -D


Vite 对 postcss 的处理,用来添加浏览器前缀,需要安装 `postcss` 和 `postcss-preset-env`。  

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16489651769542.jpg)

在使用 postcss 时,需要在根目录创建一个配置文件 `postcss.config.js`:      

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16489652259662.jpg)


### Vite 对 TypeScript 的支持   

Vite 对 TypeScript 是原生支持的。    

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16489662171165.jpg)

### Vite 本地服务器的原理     

Vite 在本地搭建了一个服务器,基于 Connect 库,Vite 会将我们编写的 `mul.ts`、`title.less` 文件分别转换(编译)为 **ES6 的 js 代码**。  

当浏览器向本地服务器请求数据时,Vite 会将这些请求**拦截**,并**转发**给编译后的 ES6 js 代码,再将这些代码返回给浏览器。   

### Vite 对 Vue 的支持   


![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490002332134.jpg)

Vite 在处理 Vue 3 单文件组件(`App.vue` 文件)之前,需要安装插件 `@vitejs/plugin-vue`。  

安装插件之后,需要在根目录下创建一个配置文件 `vite.config.js`。   

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490016375055.jpg)

在 coderwhy 老师的视频中,添加配置文件后,运行 `npx vite` 还是会报错,这是因为这个插件还会依赖一个模块 `@vitejs/plugin-vue`。   

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490014994457.jpg)

但在新版的 Vite 中,已经不需要安装这个模块 `@vitejs/plugin-vue` 了。  

### Vite 打包项目  

完成开发后,在将项目部署到服务器之前,需要对项目进行打包(构建),Vite 也提供了打包的命令:  

npx vite build


测试打包之后的文件是否有问题,可以运行另外一个命令:  

npx vite preview


在 `package.json` 的 scripts 字段添加相关的配置,就可以把之前的预览命令由 `npx vite preview` 替换为 `npm run preview`。    

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490026147789.jpg)


### ESBuild 解析  

Vite 打包的速度非常快,是因为它用到了 ESBuild。   

ESBuild 有点像是之前学习过的 Babel,但相对来说,ESBuild 的速度更快。      
ESBuild 是用 Go 语言实现的,而不是 JS。   

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490029090297.jpg)


![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490033471983.jpg)


### Vite 架手脚

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490036509066.jpg)


全局安装脚手架: 

npm install @vitejs/create-app -g


安装 Vite 脚手架之后,在终端输入 `create-app + 项目名称`,就可以快速创建出一个使用 Vite 作为构建工具的项目。   

![](https://article-picbed-1302715071.cos.ap-guangzhou.myqcloud.com/2022/04/04/16490040081962.jpg)