前端框架 Vue3 学习笔记(二)

注:学习笔记来自 coderwhy 老师的 Vue3 课程。

Vue3 组件化开发

在 VS Code 中可以安装的两个提升 Vue 开发效率的插件:

  • Vue VSCode Snippets
  • Vetur

安装 Vue VSCode Snippets 插件后,在 App.vue 文件中输入 vbase-css,就可以快速插入下面的代码片段:

想在 App.vue 中使用其他的组件(拆分出去的组件),例如 Header.vueMain.vueFooter.vue,有两种方法:

  • Header.vueMain.vueFooter.vue 注册成全局组件(不推荐)
  • 局部注册

组件的通信

父子组件间的通信:

  • 父组件传递给子组件:通过 props 属性;
  • 子组件传递给父组件:通过 $emit 触发事件。

父组件传递给子组件

父组件传递给子组件 - 字符串数组

这里的字符串数组,指的是:子组件的 props 字段传入的是一个数组

父组件传递给子组件 - 对象类型

子组件的 props 的值为对象类型时,可以对传入属性的值做更多的限制:

  • 比如指定传入的 attribute 的类型
  • 指定传入的 attribute 是否必传
  • 指定的 attribute 没有传入值时,它的默认值 default

传入 attribute 的值可以是哪些类型?

在 vue 文件,template 元素中的属性采用驼峰命名法是没问题的,因为 template 的代码是由 vue-loader 解析的,而不是由浏览器解析。

但建议还是采用 短横线分隔命名 的命名方式。

非Prop的Attribute

当我们传递给一个组件某个属性,但是该属性并没有定义对应的 props 或者 emits,就称之为 非Prop的Attribute。

常见的包括 class、style、id 属性等。

这里需要注意:
div 元素都有 class 属性;而自定义的组件,例如 <show-message> 是没有 class 属性的。

非Prop的Attribute 有 3 种情况:

  • 当(子)组件有单个根节点时,非Prop的Attribute将自动添加到根节点的 Attribute 中。这个称为 Attribute 继承。
  • 如果不希望(子)组件的根元素继承 attribute,可以在(子)组件中设置 inheritAttrs: false

  • 多个根节点的 attribute:

这里的多个根节点,是指子组件的 template 包含的多个标签,没有包裹到一个 div 标签里面。

子组件传递给父组件

案例:子组件中创建两个按钮,当按钮发生点击后,父组件中的 counter 计数器的数字相应发生变化。

this.$emit() 除了可以给父组件传递事件,还可以传递其他的参数,例如下图的 this.num。

非父子组件的通信

非父子组件的通信,主要有两种方式:

  • Provide/Inject
  • Mitt 全局事件总线

Provide 和 Inject

Provide/Inject 用于非父子组件之间共享数据:

  • 比如有一些深度嵌套的组件(层级比较深),子组件想要获取父组件的部分内容;
  • 在这种情况下,如果我们仍然将 props 沿着组件链逐级传递下去,就会非常的麻烦;

对于这种情况,我们可以使用 Provide 和 Inject:

从父组件向孙组件传递数据,最简单的用法,父组件的 provide 字段传入一个对象,孙组件的 inject 字段传入对象的 key。

如果父组件的 provide 需要用到来自 data 字段的数据,那么原先传入 provide 的对象就需要改写成一个函数。

如果我们希望父组件 provide 提供的数据变成响应式,一般会用到 computed 函数,当传入的参数发生变化时(依赖发生变化),computed 就会重新计算一个新的值。

computed 函数返回的是一个 ref 对象。

全局事件总线 mitt 库

eventBus:事件总线

总线这个概念来源于操作系统。

需要安装第三方库 mitt:

npm install mitt

安装 mitt 库之后,在根目录创建一个文件 eventbus.js,导入 mitt 函数。

案例需求:当我们点击 About 组件中的按钮时,HomeConnect 组件可以监听到点击事件。

About 和 HomeConnect 两个组件都要引入前面安装的 mitt 库。

发射多个事件,可以使用通配符接收所有事件。

mitt 的事件取消

取消掉之前注册的函数监听

插槽 Slot

插槽的使用过程其实是抽取共性、预留不同
会将共同的元素、内容封装在组件内;
同时会将不同的元素使用 Slot 作为占位,让外部决定到底显示什么样的元素。

如何使用插槽呢?

  • Vue 中将 <slot> 元素作为承载分发内容的出口;
  • 在封装组件中,使用特殊的元素 <slot> 就可以为封装组件开启一个插槽;
  • 该插槽插入什么内容取决于父组件如何使用。

插槽的基本使用

插槽的默认内容

使用多个插槽,每个插槽分别显示不同的内容。

动态插槽名:有时候在一些高级组件中,我们给 <slot> 的插槽名 name 属性赋的值不是写死的,会通过一个配置文件来传入一个值。

使用动态插槽名的时候,template 元素的 v-slot 绑定的插槽名需要加一个 [],因为这绑定的是一个变量

具名插槽的缩写

v-slot:可以简写为一个井号 #。

渲染作用域

Vue 有一个渲染作用域的概念。

看下面的一个小例子:

button 虽然最终会替换掉插槽中的 slot 元素(button 的内容虽然最终会显示在 slot 的位置),但它是在 App.vue 中完成编译的,不能调用子组件 data 中的 title。

如果父组件想使用来自子组件的数据,需要用到作用域插槽。

作用域插槽

在用到一些第三方 UI 库,或者封装高级组件的时候会用到作用域插槽。

父组件可以自由控制子组件插槽最终的显示效果。

这里的 slotProps 可以更换为其他的名字,但通常情况下就是使用 slotProps。

独占默认插槽的缩写

默认插槽 default 和具名插槽混合

动态组件

需求:当我们点击父组件中的 3 个按钮时,下方的子组件(页面)会跟着切换。

实现需求的 3 个方法:

  • 使用条件判断
  • 使用动态组件
  • 使用 Vue 路由(对简单需求来说,路由的方法比较复杂,没必要)

动态组件:

component 是 Vue 内置的组件,通过绑定 is 属性可以实现子组件间的切换。

这种方法,相比条件判断更为简洁。

认识 keep-alive

keep-alive 是 Vue 内置的组件,不需要自行定义。

前面点击按钮切换子组件的案例中,每次切换不同的子组件时,原先创建的子组件都会被销毁。

如果我们想在切换的时候,保留当前子组件的状态,例如保留我们在子组件中输入的内容、或是点击按钮累计得到的数字,这时就需要用到 keep-alive。

它可以将子组件销毁之前的状态缓存起来,这样当我们再次切换回子组件的时候,就可以看到切换前的状态啦。

keep-alive 的属性

keep-alive 配置 include 或 exclude 属性时,需要在组件中添加一个 name 字段,指明组件的名称。

keep-alive 不设置 include 属性时,默认都是会缓存的。

Webpack 的代码分包

运行 npm run build 对项目进行打包时,会得到两个 js 文件,一个是 app.js,一个是 chunk.js

随着我们自己编写的代码逻辑变多,打包得到的 app.js 文件会越来越大,在生产环境中会影响用户体验。

对于一些不需要立即使用的组件,可以单独对它们进行拆分,拆分成一些小的代码块 chunk.js,会在需要用到的时候从服务器加载下来。

打包生成的 chunck-vendors.js 文件,里面是项目用到的第三方的包,例如 vuejs、vue-router、axios。

为了缩小 app.js 的体积,我们可以使用 Webpack 的分包操作,将我们自己编写的代码单独打包为一个文件,而不是都打包到 app.js 文件中。

通过 import 函数导入的模块,后续 Webpack 对其进行打包时就会进行分包的操作。

在打包后得到的 dist 文件夹中会多出一个 chunk-哈希值 的 js 文件。

import 函数的返回值是一个 Promise,就可以用 then 方法。

讲这个知识点有什么用呢?

Vue 的异步组件,就是基于 import 函数来实现

Vue 异步组件

对 Vue 组件(.vue 文件)利用 Webpack 进行分包,需要用到异步组件

从 Vue 框架中导入一个函数 defineAsyncComponent。

调用函数 defineAsyncComponent 时,可以传入两种类型的参数:

  • 工厂函数(更常用)
  • 传入一个对象类型

defineAsyncComponent 函数传入工厂函数的写法:

defineAsyncComponent 函数传入对象的写法:

直接编写异步组件的情况比较少,一般是在配置路由的时候,把路由中的组件配置成异步组件。

异步组件和 Suspense 结合使用

Suspense 是悬念、悬而未决的意思。

Suspense 是 Vue 内置的全局组件,有两个插槽:

如果 default 插槽内的异步组件可以正常显示,那么就显示 default 插槽的内容;
如果 default 异步组件暂时无法显示,那么就会显示 fallback 的内容。

下图的 <loading /> 是自定义的组件加载过程中会在页面显示的占位组件。

refs 的使用

获取组件内的元素 h2 或者子组件实例 nav-bar

子组件获取父组件或根组件,需要使用 $parent 和 $root。

子组件 NavBar.vue 获取父组件 App.vue 的代码写法。

这个比较少用,稍微了解一下就好。

认识生命周期

什么是生命周期?

每个组件都可能会经历从创建、挂载、更新、卸载等一系列的过程。

生命周期的流程

缓存组件的生命周期

缓存组件指的是 keep-alive。

前面说到,keep-alive 可以保存 A 组件在切换之前的状态,之后重新用到 A 组件的时候,可以直接看到组件切换前保存的数据。

这个过程意味着,从 A 组件切换到 B 组件的时候,A 组件不会被销毁,因而不会调用 unmounted()**;从 B 组件切换回 A 组件的时候,A 组件不需要重新创建,因此也不会调用生命周期中的 created()**。

对于这两种情况,我们会使用另外两个生命周期函数:

  • activated()
  • deactivated()

还是上面的例子,当我们从 A 组件切换到 B 组件时,会调用 deactivated();从 B 组件切换回 A 组件时,会调用 activated()。

组件的 v-model

这部分内容有点难。

自定义组件的 v-model:

等价于两个步骤,v-bind 绑定 modelValue,同时绑定一个事件 update。

自定义组件 PhInput 中,使用 props 接收来自父组件传入的 modelValue。

自定义组件中的 input 元素使用双向绑定 v-model,需要用到计算属性 computed

Vue3 过渡&动画实现

如果我们希望给单元素或者组件实现过渡动画,可以使用 transition 内置组件来完成动画。

一个简单的例子:点击按钮,实现文字淡入淡出的效果

将添加动画的元素包裹在内置组件 transition 中

Vue 的 transition 动画

transition 组件的原理

transition 组件的原理,需要了解,面试有可能会问到。

下图的 DOM 插件、删除操作,就对应我们前面的显示和隐藏。如果 Vue 没有找到 JS 钩子或者 CSS 过渡/动画,显示和隐藏就会立即执行,不会有淡入或淡出的效果。

过渡动画 class

实现过渡动画,常用的 6 个 class:

没有给 transition 组件添加 name 属性的时候,那么所有的 class 是以 v- 作为默认前缀。

Vue 官方提供的示意图,帮助我们理解内置动画的原理:

过渡 CSS 动画

前面是通过 transition(过渡) 来实现动画效果,另外我们也可以通过 animation 来实现。

过渡动画 transition 本质上是定义了两帧,第一帧是透明度为 0,第二帧是透明度为 1,即从透明度为 0 到透明度为 1。

使用 animation 动画,不需要像前面使用 transition 过渡动画那样定义 v-enter-from 和 v-leave-to,只需要定义 v-enter-active 和 v-leave-active

使用 @keyframes 定义帧动画,后面的 bounce 是帧动画的名称,可以自定义。

过渡动画 transition 和 CSS 动画是可以同时存在的,即一个元素可以同时设置过渡动画和 CSS 动画。

同时设置了过渡动画和 CSS 动画,如果两个动画的时长是不一致的,就会出现某一个动画执行结束时,另外一个动画还没有结束,导致最终呈现的动画出现一些问题。

这时我们可以在 transition 组件中添加一个额外的属性 type,指定时长更长的动画,保证添加的两个动画可以正常结束。

transition 组件使用 duration 属性来指定过渡的时间,duration 可以传入两种类型的值,一个是 number 数字类型,一个是对象类型。

使用数字类型时,duration 属性前面需要加多一个英文的冒号: ,表示值为 number 类型而非 string 类型。

设置了 duration 属性后,在 <style> 标签中设置的过渡时间就会失效。

duration 设置的时长单位为毫秒。

transition 过渡的 mode 属性

点击按钮实现两个元素来回切换,需要设置一个 mode 属性,否则一个元素消失的同时,另外一个元素会进来,两个动画的执行没有先后顺序,整体的动画会看起来很丑。

mode 有两个值,一个是 out-in,一个是 in-out,前者是上一个元素消失后,下一个元素才出来;后者是下一个元素进来之后,上一个元素才消失。

上面两个 h2 元素切换的例子,也可以把两个 h2 元素替换为两个组件:

组件间的切换需要用到动态组件,使用 Vue 内置的 component 组件,is 绑定一个三元表达式。

apear 初次渲染

默认情况下,初次渲染(刷新页面的时候)是不会有动画的,如果希望初次渲染也有动画,可以在 transition 组件中添加一个 appear 属性。

这里的 appear 是 :appear="true" 的缩写。

第三方动画库 animate.css

跨平台:做了很多 CSS 的兼容。

如何使用 Animate 库呢?

  • 安装 animate.css
  • 导入 animate.css 库的样式
  • 使用 animation 动画或者 animate 提供的类

安装动画库:

打开终端,在项目根目录下输入

npm install animate.css

安装好之后,在入口文件 main.js 中导入 animate.css

animate.css 库有两种用法:

  • 在 v-enter-active 和 v-leave-active 中配置 animation 名称、时长和动画模式(帧动画)
  • 直接使用库定义好的 class

如果是第一种用法,在 App.vue 中给 transition 组件添加 name 属性,在样式部分配置 v-enter-active 和 v-leave-active,设置 animation 帧动画。

帧动画的名称或效果,可从网站 https://animate.style/ 查看,点击对应的动画名称,可以预览动画的效果。

确认想要使用的效果,可以复制动画的名称,并在 App.vue 的样式,给帧动画设置动画时长和动画模式。

第二种使用 animate.css 库的方法:直接使用动画库帮我们定义好的 class。

通过这种方式使用 animate.css 库,我们不需要给 transition 组件添加 name 属性。

点击动画名称右侧的「复制」按钮,可以复制动画对应的 class 类名。

将 class 分别粘贴到 enter-active-classleave-active-class,就可以应用动画了。

不过每一个动画的前面都要加多一个 animate__animated,才能真正让动画生效。

之所以每个类名前面要加上 animate__animated,是因为在 animate.css 库中,这个类名定义了动画的持续时间,有了时间,动画才能真正生效。

如果想对 animate.css 库中的动画进行反转,可以在 App.vue 的样式部分,给动画的类名添加 animation-direction 属性,设置反转。

如果同时存在自定义的 class,以及 name 定义的 class,那么自定义 class 的优先级是更高的

认识 gsap 库

gsap 是一个使用 JS 编写的动画库。

之所以要使用 JS 动画库,是因为前面介绍的 CSS 动画库不够灵活,在涉及样式的值需要发生变化,或是比较复杂的动画时,CSS 动画库用起来就不够方便。

安装 gsap 库的命令:

npm install gsap

App.vue 的 script 标签中导入 gsap 库:

import gsap from 'gsap';

transition 组件在帮助我们执行动画的时候,它其实会回调很多动画生命周期的钩子。

before-enter 相当于动画进入之前:v-enter-from
enter 相当于动画进入之时:v-enter-active
after-enter 相当于动画进入之后:v-enter-to

before-leave 相当于离开之前:v-leave-from
leave:v-leave-active
after-leave:v-leave-to

在这些钩子中使用 gsap 库,gsap 库常用的两个方法——from 和 to。

使用 gsap 库对应的 api:

传入钩子 enter(){} 的 el,是 element 的缩写。

gsap.from() 需要传入两个参数,一个是 target,指定我们要把动画设置在哪个元素上;一个是对象。

gsap 官网提供的 JS 和 CSS 对照表:

左侧是 gsap 库中 JS 的写法,右侧是对应的 CSS 样式含义。

在使用 js 动画的时候,一般会给组件添加 :css="false",让 Vue 跳过 CSS 的检测,让我们原先给组件添加的 CSS 动画失效。

小案例:gsap 实现数字滚动递增动画

这个动画,在很多后台管理系统中会用到。

监听器 watch,用来监听 counter 的变化,当 counter 发生变化,showNumber 也会随之发生变化,不过因为我们给 showNumber 设置了过渡时间 duration,它会随着时间流逝一点点地变成和 counter 一样的值,这样就实现了数字滚动递增动画的效果。

这里还有一个小细节,数字滚动变化的时候,它不是整数而是小数,为了得到整数,可以在后面加多 toFixed(0),保证取整。

注意:这里用到的计算属性并不是必须的。

列表的过渡

使用 Vue 内置的 <transition-group> 组件。

动画案例中讲到了一个类似算法的东西:如何实现在数组中随机插入一个数字?

需要生成一个随机数,同时用到数据的 splice() 方法。

Math.random() 可以随机生成 [0,1) 区间的小数。

Math.random() 乘以数组的长度 length=10,可以得到 [0,10) 之间的随机数。

再在最后向下取整 Math.floor(Math.radom() * this.number.length),可以得到 0 到 10 之间的整数,不包括 10,这些数字其实就对应数组的索引值,后面结合数组的 splice() 方法,在数组中随机的位置插入数字。

插入的数字会从下而上,删除的数字会从上而下,与此同时,如果我们想让数字在(左右)移动时,也能有动画,该怎么做呢?

可以在样式中多增加一个 v-mode,过渡 transition 设置的是位移动画。

当然,即便在样式中加上 v-move,在移除数字的时候,向左移动的数字还是没有动画的,这是因为被移除的数字,它是块级元素,在移除的过程中依旧会占据位置,导致右侧的数字想移动的时候被「挡」住了,只能等到数字移除后,一瞬间跳过去,就看不到我们想要的慢慢向左的移动动画

为了解决这个问题,我们可以额外给 v-leave-active 增加一个绝对定位,由于绝对定位的元素会脱离标准流,不会占据位置,这样就让能右侧的数字可以正常向左👈移动了。

打乱数字

给现有的动画案例,再添加一个额外的效果:打乱数组中所有元素的位置,也叫洗牌,英文是 shuffle。

这需要用到一个第三方库 lodash,使用 npm 安装 lodash 库。

npm install lodash    
```    

安装之后,在 js 部分导入 lodash 库:   

import _ from ‘lodash’;


使用 _ 中的 shuffle() 方法,传入数组,将得到的值重新赋值给数组,就能打乱数组中元素的位置。   

![](https://img.penghh.fun/2022/07/16/16573414229513.jpg)

### 列表的交错过渡动画案例


![](https://img.penghh.fun/2022/07/16/16573464340722.jpg)


当我们在 input 输入框中输入英文字母(关键词 keyword)时,下面的无序列表只显示包含英文字母的文本。 

为实现过滤的效果,我们要用到 filter 高阶函数。 

![](https://img.penghh.fun/2022/07/16/16574475189848.jpg)

其中的 indexOf() 可以返回我们输入的英文字母在字符串中首次出现的位置。  

![](https://img.penghh.fun/2022/07/16/16574473537414.jpg)

这个案例中,制作动画用到了生命周期钩子,`beforeEnter() {}` 只需要传入 el 参数,不像 enter 和 leave 钩子需要传入 done 参数。     

![](https://img.penghh.fun/2022/07/16/16574481638520.jpg)