万字长文带你全面掌握Vue3

13,590 阅读21分钟

在2020年9月19日,vue更新了3.0的正式版,不知不觉,已经过去了好几个月了,作为一位前端切图仔,是时候开始学习了,每次抱着准备学、再等等、明天说的想法,成功在发布了两个多月的时候来认真学习了一波,这里来总结一下vue3到底有哪些更新,来帮各位没有时间去了解vue3的朋友来一次快速入门。

如何看待vue3?

vue2.x是一个比较稳定的版本,也是很长一段时间我们在使用的版本,社区生态已经十分完善了,所以,如果我们暂时还不必须去着急升级到vue3,毕竟等待vue3的生态成熟,还需要一段时间的积累,但是作为前端领域必不可少的一门技能,当然希望能够提前去接触到,毕竟前端的技术迭代更新就是这么快。

所以我们来看看vue3相对于vue2到底有哪些优势和特性吧。方便等到时候社区完善之后,我们可以直接在工作中去使用这些技术,毕竟自vue3一问世,就受到了行业各种大佬的关注。所以不管是单纯的作为api开发工程师去体验使用vue3,或者想要去学习尤大的编程思想,我们都有必要去了解学习一下,本文都将使用vue3的语法来写,下面是vue3的官网地址

  • vue3.x官网地址

  • 也可以到当前版本的官网在左上角点击切换到3.x-beta版本

怎么创建一个vue3的项目

  • 使用官方的最新版的vue-cli,升级之后在创建项目的时候会让你选择版本,选择vue3版本即可,如下图:

只需要选择3的版本然后和vue2一样的选择,一路绿灯即可完成项目创建,这样的项目和vue2并无太多区别,同样是利用webpack来构建项目,相信这样的上手方式将会非常简单,但是vue3还提供了另一种创建方式:

  • 第二种就是vite的方式生成一个项目了,做过大项目的同学都知道,webpack在我们本地开发的过程中,每一次做细微的修改,都会造成很长时间的重新打包,对于很多大型项目,这样消耗的时间实在太久了,这也是长久以来的一个痛点,于是,vue3携带vite出世了,vite依然是基于node来工作的,原理是利用浏览器现在已经支持es6的import了,遇到import会发送一个http请求去加载文件,vite拦截这些请求,做一些预编译,省去了webpack冗长打包的时间,提升开发体验,让本地开发更为高效
    • 这时候可能有人会问,这样webpack还有用么,那么这个其实毋庸置疑,当然是有用的,第一个vite的生态还不完善,暂时还不能保证其稳定性,第二依赖vite进行本地开发,在上线前的打包工作依然需要webpack的支持来使用,所以,webpack依然需要,当然vite这样的新颖模式依然是值得期待的,希望能够早点让这个技术更成熟,让我们的本地开发更为高效。

项目目录

|-node_modules       -- 所有的项目依赖包都放在这个目录下
|-public             -- 公共文件夹
---|favicon.ico      -- 网站的显示图标
---|index.html       -- 入口的html文件
|-src                -- 源文件目录,编写的代码基本都在这个目录下
---|assets           -- 放置静态文件的目录,比如logo.pn就放在这里
---|components       -- Vue的组件文件,自定义的组件都会放到这
---|App.vue          -- 根组件,这个在Vue2中也有
---|main.ts          -- 入口文件,因为采用了TypeScript所以是ts结尾
---|shims-vue.d.ts   -- 类文件(也叫定义文件),因为.vue结尾的文件在ts中不认可,所以要有定义文件
|-.browserslistrc    -- 在不同前端工具之间公用目标浏览器和node版本的配置文件,作用是设置兼容性最低适配版本
|-.eslintrc.js       -- Eslint的配置文件,不用作过多介绍
|-.gitignore         -- 用来配置那些文件不上传到git的文件进行管理
|-package.json       -- 命令配置和包管理文件
|-README.md          -- 项目的说明文件,使用markdown语法进行编写
|-tsconfig.json      -- 关于TypoScript的配置文件
|-yarn.lock          -- 使用yarn后自动生成的文件,由Yarn管理,安装yarn包时的重要信息存储到yarn.lock文件中

setup() 和 ref()

编辑器本人用的vscode,要想有vue3的语法提示,在vscode的商店里面下载,**Vue 3 Snippets ** 插件即可。

  • 因为是学习vue3,所以下面的语法都将使用vue3来书写

首先我们先来写一个简单的改造下app.vue页面,老规矩,全部清空,自己来操作,下面来写一个简单的店名系统功能来分析下新语法:

首先来对比下语法上有啥不同

  • 在vue2,要实现这样一个程序,需要学生数组选中人、然后methods去定义一个选中方法selectStuFn
    • 但是在vue3中,直接全部写在setup函数中就行,首先先来说说这个setup是什么,这个其实就是个生命周期的钩子函数,相对于vue2,他就等于vue2的beforeCreatecreate两个生命周期函数,在vue3中他不仅仅是一生命周期函数,同时我们需要通过这个函数来定义vue2中的data,methods,watch,computed属性,所以我们上面说到的定义的两个属性和方法都定义到了其中,但是最后都需要return回去,并且写法有很大的一点不同是,我们对其赋值或者取值的时候必须是通过**.valuede 写法去拿或者取,可以看上图,并且最终我们不管他是属性还是方法都需要return**回去之后才能使用
  • 在vue2中定义在data里面的值就会自动变成响应式,不需要我们做什么操作,就可以在template里面直接使用
    • 在vue3中,如上图,我们需要通过ref关键字进行包括,这样的操作才能将它定义为一个响应式的数据,
    • ref函数是一个把普通变量变成Proxy响应式变量的函数
    • ref的另一个用法可以调用原生的DOM

所以我们对setupref做一个总结:

  1. setup函数其实是一个生命周期钩子,它对应的其实就是Vue2中的beforeCreate和create,并且他是vue3的composition API的入口函数。
  2. 在vue3中我们通过这个函数来定义vue2中的data,methods,watch,computed属性
  3. setup函数必须有返回值,必须返回一个对象,对象里包含所有在template模板中需要使用到的属性(包含data,methods等)
  4. 在setup里面通过ref生命的响应式数据,去取值或者赋值的时候必须通过**.value的方法去拿,但是template却不需要使用.value**
  5. ref也可以调用原生dom
  6. setup接收第一个参数是props,用于接收props,也就是定义在组件上的属性(同vue2),但是接收的props必须先在props属性中定义,否则是不会被接收到的
  7. setup接收的第二个参数是context,在js里面这个形参代表了上下文,它暴露组件的 property,他就是一个普通的javaScript对象,在这里面我们可以取到attrs,** slots**,** emit**,等属性,所以可以干嘛我想各位都知道了。

!需要注意的是,props是响应式的数据,你不能使用 ES6 解构,因为它会消除 prop 的响应性。我们可以尝试一下将 props 进行 ES6 解构,会发现控制台报警了,所以,要结构props需要用到setup 函数中的 toRefs 来安全地完成此操作,后面再来说这个函数。


不难看出,上面的写法对于vue2而言,很多人可能会觉得比较麻烦,甚至有些疑惑为何需要使用**.value的方式,但是在模板中却又不需要,这样的写法似乎有些反人类了,第二个我们如果定义很多还需要return很多值出去,都会觉得很麻烦,那么我们有没有简单的写法可以解决它呢,答案是有的 ,所以,我们来看看vue3的下一个新的API**,看看如何解决这个问题。

使用reactive函数优化程序

上面看到,写法相对有些不是很方便,下面我们通过这个函数把他进行优化,不再使用**.value**的方式。

在上图,我用箭头指出了不一样的地方,可以为大家总结下面几点

  1. 不再使用ref对属性进行申明响应式,而是和类似vue2的写法,我只需要定义一个data对象即可,想要的属性都放在data,最后统一返回data即可。
  2. 不再使用**.value的方式而改用data.**这样的方式,更方便自己理解,当然同理,template也需要加上,对我个人而言,,我觉得这种的写法更为清晰,更好理解。
  3. 不需要再return一堆东西了,只需要return一个对象就可以了,写法更加简洁。

!这时候又有同学会问了,本来template不需要使用**.value怎么现在反而需要加data.了,这不是更麻烦了么?可以不去在template**加其他东西而直接使用属性就可以么? 当然可以。

  • 需要做到这种,有同学就会想到,只要我不返回对象,返回所以属性不就可以了吗?你想的没错,直接return所以属性就行了,就可以实行了,但是怎么实现呢?
    1. 有同学就会想,直接结构赋值不就好了吗,直接rerurn {...data}这样不就可以么?我可以直接告诉你,这样不可以,那么是为什么呢,这里和上面的props一样的原理,因为我们定义的是响应式的数据,如果直接这样结构赋值就会造成,响应式特性的破坏,造成数据不再是响应式,这样点击按钮就没有任何变化了,所以我们不可以这样做。
    2. 那么我们如何实现呢?也很简单,使用官方提供的 **toRefs()**函数即可完成,我们再来改造一下。

toRefs()函数对reavtive()函数解构返回

我们对其进行简单的改造你就可以明白这个方法的作用了。

  • 使用这样的方式就可以在模板中直接使用了
  • 不难想象这个方法就是为了通过其方法结构后不去破坏他的响应式特性,就是这么简单。

vue3的生命周期

  1. 什么是生命周期

    1. vue是组件化开发,一个组件从诞生到消亡的过程就是组件的生命周期。
    2. 生命后期和人的出生到死亡是一样的,不同时期可以做不同的事情,就是这样的道理。
  2. vue2和vue3的生命周期对比

    1. vue2vue3vue2和3的部分差异比较
      beforeCreatesetupsetup() :开始创建组件之前,在beforeCreate和created之前执行。创建的是data和method
      createdsetup
      beforeMountonBeforeMount组件挂载到节点上之前执行的函数。
      mountedonMounted组件挂载完成后执行的函数。
      beforeUpdateonBeforeUpdate组件更新之前执行的函数。
      updatedonUpdated组件更新完成之后执行的函数。
      beforeDestroyonBeforeUnmount卸载之前执行的函数。相比改名了
      destroyedonUnmounted卸载之后执行的函数。
      activatedonActivated被包含在中的组件,会多出两个生命周期钩子函数。被激活时执行。
      deactivatedonDeactivated比如从 A 组件,切换到 B 组件,A 组件消失时执行。
      errorCapturedonErrorCaptured当捕获一个来自子孙组件的异常时激活钩子函数。
      onRenderTrackedvue3新增的周期用于开发调试使用的
      onRenderTriggeredvue3新增的周期用于开发调试使用的
    2. 先简单的对比,我们发现了这些不同之处

      • vue2的beforeCreatecreate变成了setup

      • vue2的destroyedbeforDestroy变成了onUnmounted:尤大的解释是Unmounted更加语义化,卸载的意思比vue2的销毁更加形象

      • 除了setup外大部分还是vue2的名字,只是在前面加了个on

      • 关于调试函数,目前官方文档也没有过多讲解,大部分的博主对于这个两个函数也非常模糊,这里我自己使用测试之后发现,这两个函数都是关于组件状态发生变更之后可以追踪到各个响应式数据的前后变更状态很变更顺序,并且都拥有一个参数event,所以个人觉得这个个人觉得就是状态跟踪函数,目的是为了在我们本地开发的时候准备看出各个响应式数据的变化过程。当然,这个后面的官方文档肯定会为大家解惑。

vue3的watch的变化和watchEffect()

watch侦听器,我们已经很常见了,基础用法就是监听一个值的新旧值的变化,在vue2里面采用的是一个对象的形式,然后在里面去监听不同的值,在vue3已经发生了改变,让我们一起来看看

import {watch} from 'vue'

watch(a, (newVal, oldVal) => {
	console.log(newVal, '===', oldVal)
})

可以看到vue3watch基础用法已经改变,想要使用watch,必须先引入,然后实际上,vue3现在的语法和各类ui框架的按需引入一样,每个API都是要用什么引入什么这样的方式,后面我们再来看看这样的好处

然后看看语法,这里的wacth函数接收两个参数,第一个参数是我们要检测的值,第二个是回调函数,和以前一样拥有新旧两个值。

那么同时,vue3watch支持同时监听多个值,以数组的形式,如下:

import {watch} from 'vue'

watch([a, b], ([newValA, newValB], [oldValA, oldValB]) => {
	console.log(newValA, newValB, '===', oldValA, oldValB)
})

监听多个值,对应的回调函数的形参里的新旧值就也是多个,这样的监听会让我们的业务做某些对比判断的时候更加的灵活,上面就是vue3的watch的基本用法了,当然,还有一个地方需要特别注意的是。

  • watch只能监听通过**ref()方法定义的响应式数据,通过reactive()**函数定义的将会直接报错,让我们先来看看报的错误再来说说为什么:

  • 上面这段报错是什么意思呢? 它告诉我们,watch只能监听,1:拥有getter/setter属性的值。2:一个ref属性,也就是通过**ref()申明的数据。3:一个reactive()**函数返回的对象。4:一个数组,这四种方式,那么就如上图,data.selectPeople会直接报错,就是因为我们监听这个值不属于上面四种情况,

    • 那么首先我们想为很么需要做呢?为什么不可以监听呢?我们先想想vue2中watch有一个属性叫deep深度监听,这又是为什么呢?那么深度监听的本质其实是为了解决循环引用问题,那么我们知道,vue2的深度监听实际上是用了递归来解决,这样就造成了性能不是很好,所以官方也不是很推荐使用deep,那么上面的问题就是,当你去监听一个对象或者数组的时候,前后对象就是循环引用,会发现newValoldVal的值始终一样,所以,才会出现上面这种情况,所以,要想解决上面的问题也很简单,可以通过一个函数直接return回来一个新值,这样就可以解决引用问题,如下

相比之下,vue3watch不论是从语法或者是从业务考虑,都有了新的场景和更多的使用方法,变得更加灵活了。

watchEffect呢也是vue3提出的一个新的API,语法是这样的:

import {watchEffect} from 'vue'
watchEffect(() => console.log(name))

他是一个方法,接收一个回调函数,他不需要去指定监听谁,它会去自动收集依赖,只要在回调函数中使用了的属性,在发生变化的时候,都会去触发这个回调函数,这样就会变得非常简单,在业务中,我们不必去特意的监听某某属性,而是直接把他写在其回调函数中,就可以自动帮我们收集依赖了。

Vue3的模块化

我们在使用vue2项目中,如果复用某些功能,我们会采用mixin的方式进行混入,而在vue3中,新的模块儿化的重用机制将会更加便捷,我们先来写一个简单的实时在线的时间显示功能,如下:

就是点击按钮触发一个时间显示的功能非常的简单,页面效果就是这样,相信同学们,都能轻松理解:

现在我们想要复用这段逻辑,在vue2中,如果我们采用mixin混入式的方式开发将要复用很大一部分代码逻辑,让我们看看如果vue3想要复用这个功能需要干嘛吧:

  • 第一步我们只需要把**setup()**里面我们写的这一段逻辑提取到一个ts文件即可,然后导出我们需要的方法和属性,我在这里直接导出了一个ref()包裹的对象,如果你想更细致,也可以导出data在组件中进行ref()包裹响应,而在用的时候非常简单,看看下图:

  • 我们可以看到,只需两部就完成了,只需要引入,再在**setup()**中直接return,就完成了一个功能的复用

可以说,vue3在模块复用性这里相对于vue2有了非常大的提升,这样的改变会使的组件之间的模块儿化变得异常的灵活,这样的写法你是否很期待呢?

vue3的瞬间移动组件Teleport

Teleport翻译过来就是瞬移的意思,于是,国内翻译过来就是瞬移组件的,怎么来理解这个瞬移组件呢。

  • 我们都知道的一点是,不论是vue或者是react在入口文件的html地方,都只有一个容器就是<div id='app'></div>,我们称之为入口,同时页面上的所有组件实际都是挂载在这个节点的,打开网页,我们可以看到控制到上,最外层只有有个节点就是这个app。
  • 写过弹窗组件或者消息提示组件的朋友们都知道,像这种全局只有一个状态的组件,我们不希望他混入我们的组件中,我们希望他独立于app组件,所以,在大多数优秀的ui框架中,这一类的高级组件,一般会去采用新创建一个节点的方式,抽离出app节点,这样更为容易控制,并且在使用完毕后即使的删除。那么Teleoirt组件实际上就是这样的一个东西,可以独立于app之外的新的节点,我们都知道入口文件index.html都有一个节点叫app,我们新增一个app2,如图。

然后我们怎么使用这个节点呢,我们先去创建一个简易的弹窗组件,这个组件也很简单,在页面中心展示出来就好,我们来看看这个组件和vue2有何区别吧:

  • 我们可以看到,这个组件外部包裹了一层Teleport标签,并且拥有一个to属性,那么首先,要使用瞬移组件就需要,这个标签包裹,那么to属性呢就代表我要挂载在哪个节点,如上图,我们创建了一个app2的节点,我们想要挂载在这个节点上面,我们to='app2'就可了,这样就创建了一个瞬移组件,使用方法和vue2没有任何区别,我们来看看实际效果吧:

ok,就是这么简单,就完成了一个瞬移组件的创建,这样的组件在对于一些状态单一的组件创建中,变得更加丝滑了,不会对使用的组件内部造成任何破坏,也不会出现样式污染等各类问题,非常的实用。

Vue3的异步请求组件Suspense

这个异步组件(Suspense)呢是vue3新加的一种组件,再日常的开发中,异步组件是不可缺少的一部分,例如接口请求,图片请求,各种异步操作所导致的请求都属于异步组件,在vue2中这些状态的判断都需要我们自己手动去判断,但是vue3提供这一非常贴心的组件,这个中文翻译过来就是悬念的意思,他提供了两个template 也就是两个插槽,一个是没请求回来的时候显示什么,一个是请求成功显示什么,我们来写一个看看吧,先来写个异步的组件,非常简单,这里的这个接口是一个随机返回一张图片的接口。

这里呢就是随机返回一个图片,我们来引入这个组件使用吧:

  • 使用非常简单,通过Suspense标签包裹的组件就是异步组件,在其中,提供了两个插槽,分别是成功和失败的插槽,会对应显示其中的内容,这样的写法可以方便我们少做很多冗余的判断,是不是也很好用呢

!当然,官方提示,这是一个还属于实现性的特征的API,后期可能会改变,当然,我们也需要提前知道有这样的一种写法。

关于Vue3一些小的知识点的解释

  • 什么是Composition API

    • Compositon API不是一个api,而是很多个API组合的一套API,我们统称为这名字,vue2中,我们会在methods,computed,watch,data中等等定义属性和方法,共同处理页面逻辑,我们称这种方式为Options API。这样的方式在项目过大的时候我们发现,一个methods定义数十个方法的时候,还得准备知道每个方法的this,作用,干嘛的就会变得非常麻烦,而Composition API就是解决这个问题的
    • vue3 Composition API 中,我们的代码是根据逻辑功能来组织的,一个功能所定义的所有api会放在一起(更加的高内聚,低耦合),这样做,即使项目很大,功能很多,我们都能快速的定位到这个功能所用到的所有API,
  • **ref()reactive()**的选用谁更好?

    • 这个问题其实全看个人喜好,两种方法都可以使用,个人更喜欢**reactive()**的方法,这两个之间除了写法差异并没有其他差异。
  • vue3的入口采用的createApp() 的方式有什么区别?

    • vue2中是使用new Vue()的方式,vue3使用createApp()的方式,本质是没有太大的区别,但是呢,在之前我们每次注册一些全局的组件,mixin,plugins,prototype等,都会是这样的方式,Vue.mixin(...xxx)这样的方式,会造成,全局只有一个app的实例,这样就会造成实例污染,而在vue3中,**createApp()**会返回一个全新的app,可以很好的避免这个问题。
    • 同时,这样的写法在调用的时候采用了链式调用的方式,类似于koaexpress这些框架一样,如下图:
  • Vue3如何注册全局组件?

    • app.component('componentName', component)
      
  • Vue3如何注册自定义指令

    • /** v-snine */
      app.directive('snine', {
        inserted: function (el) {
          el.snine()
        },
      })
      
  • Vue3如何全局混入

    • const app = createApp(App)
      app.mixin({
        beforeCreate() {
          console.log('我是全局mixin')
        },
      })
      
  • Vue3如何全局挂载属性和方法

    • const app = createApp(App)
      
      // 全局ctx(this) 上挂载 $axios 需要挂载在globalProperties
      app.config.globalProperties.$axios = axios
      
      
  • Vue3还有filter么?

    • Vue3已经移除了filter,木有了哦~~~
  • Vue2和Vue3的生命周期可以混用么?怎么用?

    • vue2和vue3的生命周期是可以混着一起用的,api和之前没有任何区别,但是我觉得如果你是用vue3再去写项目,我觉得就没有必要再用vue2的api了吧,当然如果你非要混合使用,可以告诉你的是,vue3的api的优先级会更好,打印各个生命周期会发现,优先执行vue3的生命周期,才会执行vue2的。
  • Vue3中可以使用vue2的写法么?

    • vue3采用渐进式开发,向下兼容,也就是说,我们可以依然使用vue2的语法来完成,这也是尤大非常贴心的为大家考虑项目升级所做的吧。

。。。。。。

总结

前端学习路漫漫,要想面向工资编程,总要去尝试学习新的技术,新的知识,学习别人的写法与思想,路漫漫其修远兮,最后放上一张简单的思维导图祝大家早日精通Vue3