之前我写了一篇博客介绍了Vue3的新个性,简略理解了一下Vue3都有哪些特色,并且在文末带大家略微体验了一下Vue3中 Compsition API 的简略应用

上一篇文章地址:紧跟尤大的脚步提前体验Vue3新个性,你不会还没理解过Vue3吧

因为这个月的月初给本人定了个小指标,学完Vue3的根本应用,并应用Vue3亲手做一个小我的项目(略微走漏一下,我制作的是一个小工具,当初曾经实现了90%了,这个月月底之前会通过博客的模式向大家展现,并且提供设计思路,大家敬请期待……),本文会频繁地比照Vue2来介绍Vue3,也将对各个API联合代码实例解说,这既是对本人常识的总结,也心愿能帮忙到大家

一、前言

大家都晓得,当初Vue3的各个版本曾经陆续公布了,并且有很多的团队曾经着手各个库的开发与Vue2向Vue3的降级,咱们当然也不能落后,所以连忙将你手中的Vue2降级到Vue3,跟着本文一起学习新的API吧

降级的办法能够点击本文结尾的文章,在上一篇文章中有个保姆级别的教程通知大家如何降级

二、注释

Vue2每次都把整个Vue导入,例如Vue2的 main.js 文件中的代码

import Vue from 'vue'import App from './App.vue'Vue.config.productionTip = falsenew Vue({  render: h => h(App)}).$mount('#app')

但很显著咱们的我的项目中不可能用到Vue所有的API,因而很多模块其实是没有用的

那么在Vue3中,对外裸露了很多的API供开发者应用,咱们能够依据本人的需要,将所须要的API从Vue中导入。例如 main.js 中的代码

import { createApp } from 'vue';import App from './App.vue'createApp(App).mount('#app')

利用了 importexport 的导入导出语法,实现了按需打包模块的性能,我的项目打包后的文件体积显著小了很多

这也是咱们本文须要对 Vue3 API 进行具体理解的起因

(1)setup

setup 函数也是 Compsition API 的入口函数,咱们的变量、办法都是在该函数里定义的,来看一下应用办法

<template>  <div id="app">      <p>{{ number }}</p>      <button @click="add">减少</button>  </div></template><script>// 1. 从 vue 中引入 ref 函数import {ref} from 'vue'export default {  name: 'App',  setup() {      // 2. 用 ref 函数包装一个响应式变量 number    let number = ref(0)        // 3. 设定一个办法    function add() {        // number是被ref函数包装过了的,其值保留在.value中      number.value ++    }        // 4. 将 number 和 add 返回进来,供template中应用    return {number, add}  }  }</script>

上述代码中用到了 ref 函数,上面会具体解说,在这里你只须要了解它的作用是包装一个响应式的数据即可,并且你能够将 ref 函数包装过的变量看作是Vue2 data 中的变量

这样就简略实现了一个点击按钮数字加1的性能


在Vue2中,咱们拜访 dataprops 中的变量,都是通过相似 this.number 这样的模式去获取的,但要特地留神的是,在setup中,this 指向的是 undefined,也就是说不能再向Vue2一样通过 this 去获取变量了

那么到底该如何获取到 props 中的数据呢?

其实 setup 函数还有两个参数,别离是 propscontext,前者存储着定义以后组件容许外界传递过去的参数名称以及对应的值;后者是一个上下文对象,能从中拜访到 attremitslots

其中 emit 就是咱们相熟的Vue2中与父组件通信的办法,能够间接拿来调用

(2)生命周期

Vue2中有 beforeCreatecreatedbeforeMountmountedbeforeUpdate 等生命周期函数

而在Vue3中,这些生命周期局部有所变动,并且调用的形式也有所扭转,上面放上一张变动图来简略理解一下

Vue2Vue3
beforeCreatesetup
createdsetup
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestoryonBeforeUnmount
destoryedunMounted

Vue3的这些生命周期调用也很简略,同样是先从 vue 中导入,再进行间接调用

<template>  <div id="app"></div></template><script>// 1. 从 vue 中引入 多个生命周期函数import {onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, unMounted} from 'vue'export default {  name: 'App',  setup() {    onBeforeMount(() => {      // 在挂载前执行某些代码    })    onMounted(() => {      // 在挂载后执行某些代码    })    onBeforeUpdate(() => {      // 在更新前前执行某些代码    })    onUpdated(() => {      // 在更新后执行某些代码    })    onBeforeUnmount(() => {      // 在组件销毁前执行某些代码    })    unMounted(() => {      // 在组件销毁后执行某些代码    })    return {}  }  }</script>

要特地阐明一下的就是,setup 函数代替了 beforeCreatecreated 两个生命周期函数,因而咱们能够认为它的执行工夫在beforeCreatecreated 之间

(3)reactive

reactive 办法是用来创立一个响应式的数据对象,该API也很好地解决了Vue2通过 defineProperty 实现数据响应式的缺点

用法很简略,只需将数据作为参数传入即可,代码如下

<template>  <div id="app">      <!-- 4. 拜访响应式数据对象中的 count  -->      {{ state.count }}  </div></template><script>// 1. 从 vue 中导入 reactive import {reactive} from 'vue'export default {  name: 'App',  setup() {       // 2. 创立响应式的数据对象    const state = reactive({count: 3})    // 3. 将响应式数据对象state return 进来,供template应用    return {state}  }}</script>

(4)ref

在介绍 setup 函数时,咱们应用了 ref 函数包装了一个响应式的数据对象,这里外表上看上去跟 reactive 如同性能截然不同啊,的确差不多,因为 ref 就是通过 reactive 包装了一个对象 ,而后是将值传给该对象中的 value 属性,这也就解释了为什么每次拜访时咱们都须要加上 .value

咱们能够简略地把 ref(obj) 了解为这个样子 reactive({value: obj})

这里咱们写一段代码来具体看一下

<script>import {ref, reactive} from 'vue'export default {  name: 'App',  setup() {      const obj = {count: 3}      const state1 = ref(obj)      const state2 = reactive(obj)    console.log(state1)    console.log(state2)  }  }</script>

来看一下打印后果

留神: 这里指的 .value 是在 setup 函数中拜访 ref 包装后的对象时才须要加的,在 template 模板中拜访时是不须要的,因为在编译时,会自动识别其是否为 ref 包装过的

那么咱们到底该如何抉择 refreactive 呢?

倡议:

  1. 根本类型值(StringNmuberBoolean 等)或单值对象(相似像 {count: 3} 这样只有一个属性值的对象)应用 ref
  2. 援用类型值(ObjectArray)应用 reactive

(5)toRef

toRef 是将某个对象中的某个值转化为响应式数据,其接管两个参数,第一个参数为 obj 对象;第二个参数为对象中的属性名

代码如下:

<script>// 1. 导入 toRefimport {toRef} from 'vue'export default {    setup() {        const obj = {count: 3}        // 2. 将 obj 对象中属性count的值转化为响应式数据        const state = toRef(obj, 'count')                      // 3. 将toRef包装过的数据对象返回供template应用        return {state}    }}</script>

但其实外表上看上去 toRef 这个API如同十分的没用,因为这个性能也能够用 ref 实现,代码如下

<script>// 1. 导入 refimport {ref} from 'vue'export default {    setup() {        const obj = {count: 3}        // 2. 将 obj 对象中属性count的值转化为响应式数据        const state = ref(obj.count)                      // 3. 将ref包装过的数据对象返回供template应用        return {state}    }}</script>

乍一看如同还真是,其实这两者是有区别的,咱们能够通过一个案例来比拟一下,代码如下

<template>    <p>{{ state1 }}</p>    <button @click="add1">减少</button>    <p>{{ state2 }}</p>    <button @click="add2">减少</button></template><script>import {ref, toRef} from 'vue'export default {    setup() {        const obj = {count: 3}        const state1 = ref(obj.count)        const state2 = toRef(obj, 'count')        function add1() {            state1.value ++            console.log('原始值:', obj);            console.log('响应式数据对象:', state1);        }              function add2() {            state2.value ++            console.log('原始值:', obj);            console.log('响应式数据对象:', state2);        }        return {state1, state2, add1, add2}    }}</script>

咱们别离用 reftoRefobj 中的 count 转化为响应式,并申明了两个办法别离使 count 值减少,每次减少后打印一下原始值 obj 和被包装过的响应式数据对象,同时还要看看视图的变动

ref:


能够看到,在对响应式数据的值进行 +1 操作后,视图扭转了,原始值未扭转,响应式数据对象的值也扭转了,这阐明 ref 是对原数据的一个拷贝,不会影响到原始值,同时响应式数据对象值扭转后会同步更新视图

toRef:


能够看到,在对响应式数据的值进行 +1 操作后,视图未产生扭转,原始值扭转了,响应式数据对象的值也扭转了,这阐明 toRef 是对原数据的一个援用,会影响到原始值,然而响应式数据对象值扭转后会不会更新视图

总结:

  1. ref 是对传入数据的拷贝;toRef 是对传入数据的援用
  2. ref 的值扭转会更新视图;toRef 的值扭转不会更新视图

(6)toRefs

理解完 toRef 后,就很好了解 toRefs 了,其作用就是将传入的对象里所有的属性的值都转化为响应式数据对象,该函数反对一个参数,即 obj 对象

咱们来看一下它的根本应用

<script>// 1. 导入 toRefsimport {toRefs} from 'vue'export default {    setup() {        const obj = {          name: '前端印象',          age: 22,          gender: 0        }        // 2. 将 obj 对象中属性count的值转化为响应式数据        const state = toRefs(obj)                // 3. 打印查看一下        console.log(state)    }}</script>

打印后果如下:


返回的是一个对象,对象里蕴含了每一个包装过后的响应式数据对象

(7)shallowReactive

听这个API的名称就晓得,这是一个渐层的 reactive,难道意思就是本来的 reactive 是深层的呗,没错,这是一个用于性能优化的API

其实将 obj 作为参数传递给 reactive 生成响应式数据对象时,若 obj 的层级不止一层,那么会将每一层都用 Proxy 包装一次,咱们来验证一下

<script>import {reactive} from 'vue'export default {    setup() {        const obj = {          a: 1,          first: {            b: 2,            second: {              c: 3            }          }        }                const state = reactive(obj)        console.log(state)        console.log(state.first)        console.log(state.first.second)    }}</script>

来看一下打印后果:


构想一下如果一个对象层级比拟深,那么每一层都用 Proxy 包装后,对于性能是十分不敌对的

接下来咱们再来看看 shallowReactive

<script>import {shallowReactive} from 'vue'export default {    setup() {        const obj = {          a: 1,          first: {            b: 2,            second: {              c: 3            }          }        }                const state = shallowReactive(obj)        console.log(state)        console.log(state.first)        console.log(state.first.second)    }}</script>

来看一下打印后果:


后果十分的明了了,只有第一层被 Proxy 解决了,也就是说只有批改第一层的值时,才会响应式更新,代码如下:

<template>    <p>{{ state.a }}</p>    <p>{{ state.first.b }}</p>    <p>{{ state.first.second.c }}</p>    <button @click="change1">扭转1</button>    <button @click="change2">扭转2</button></template><script>import {shallowReactive} from 'vue'export default {    setup() {        const obj = {          a: 1,          first: {            b: 2,            second: {              c: 3            }          }        }                const state = shallowReactive(obj)                function change1() {          state.a = 7        }        function change2() {          state.first.b = 8          state.first.second.c = 9          console.log(state);        }        return {state}    }}</script>

来看一下具体过程:


首先咱们点击了第二个按钮,扭转了第二层的 b 和第三层的 c,尽管值产生了扭转,然而视图却没有进行更新;

当咱们点击了第一个按钮,扭转了第一层的 a 时,整个视图进行了更新;

由此可阐明,shallowReactive 监听了第一层属性的值,一旦产生扭转,则更新视图

(8)shallowRef

这是一个浅层的 ref,与 shallowReactive 一样是拿来做性能优化的

shallowReactive 是监听对象第一层的数据变动用于驱动视图更新,那么 shallowRef 则是监听 .value 的值的变动来更新视图的

咱们来看一下具体代码

<template>    <p>{{ state.a }}</p>    <p>{{ state.first.b }}</p>    <p>{{ state.first.second.c }}</p>    <button @click="change1">扭转1</button>    <button @click="change2">扭转2</button></template><script>import {shallowRef} from 'vue'export default {    setup() {        const obj = {          a: 1,          first: {            b: 2,            second: {              c: 3            }          }        }                const state = shallowRef(obj)        console.log(state);                function change1() {          // 间接将state.value从新赋值          state.value = {            a: 7,            first: {              b: 8,              second: {                c: 9              }            }          }        }        function change2() {          state.value.first.b = 8          state.value.first.second.c = 9          console.log(state);        }              return {state, change1, change2}    }}</script>

首先看一下被 shallowRef 包装过后是怎么的构造


而后再来看看扭转其值会有什么变动


咱们先点击了第二个按钮,发现数据的确被扭转了,然而视图并没随之更新;

于是点击了第二个按钮,行将整个 .value 从新赋值了,视图就立马更新了

这么一看,未免也太过麻烦了,改个数据还要从新赋值,不要放心,此时咱们能够用到另一个API,叫做 triggerRef ,调用它就能够立马更新视图,其接管一个参数 state ,即须要更新的 ref 对象

咱们来应用一下

<template>    <p>{{ state.a }}</p>    <p>{{ state.first.b }}</p>    <p>{{ state.first.second.c }}</p>    <button @click="change">扭转</button></template><script>import {shallowRef, triggerRef} from 'vue'export default {    setup() {        const obj = {          a: 1,          first: {            b: 2,            second: {              c: 3            }          }        }                const state = shallowRef(obj)        console.log(state);        function change() {          state.value.first.b = 8          state.value.first.second.c = 9          // 批改值后立刻驱动视图更新          triggerRef(state)          console.log(state);        }              return {state, change}    }}</script>

咱们来看一下具体过程


能够看到,咱们没有给 .value 从新赋值,只是在批改值后,调用了 triggerRef 就实现了视图的更新

(9)toRaw

toRaw 办法是用于获取 refreactive 对象的原始数据的

先来看一段代码

<template>    <p>{{ state.name }}</p>    <p>{{ state.age }}</p>    <button @click="change">扭转</button></template><script>import {reactive} from 'vue'export default {    setup() {        const obj = {          name: '前端印象',          age: 22        }        const state = reactive(obj)            function change() {          state.age = 90          console.log(obj); // 打印原始数据obj          console.log(state);  // 打印 reactive对象        }        return {state, change}    }}</script>

来看看具体过程


咱们扭转了 reactive 对象中的数据,于是看到原始数据 obj 和被 reactive 包装过的对象的值都产生了变动,由此咱们能够看出,这两者是一个援用关系

那么此时咱们就想了,那如果间接扭转原始数据 obj 的值,会怎么样呢?答案是: reactive 的值也会跟着扭转,然而视图不会更新

由此可见,当咱们想批改数据,但不想让视图更新时,能够抉择间接批改原始数据上的值,因而须要先获取到原始数据,咱们能够应用 Vue3 提供的 toRaw 办法

toRaw 接管一个参数,即 ref 对象或 reactive 对象

<script>import {reactive, toRaw} from 'vue'export default {    setup() {        const obj = {          name: '前端印象',          age: 22        }        const state = reactive(obj)            const raw = toRaw(state)        console.log(obj === raw)   // true    }}</script>

上述代码就证实了 toRaw 办法从 reactive 对象中获取到的是原始数据,因而咱们就能够很不便的通过批改原始数据的值而不更新视图来做一些性能优化了

留神: 补充一句,当 toRaw 办法接管的参数是 ref 对象时,须要加上 .value 能力获取到原始数据对象

(10)markRaw

markRaw 办法能够将原始数据标记为非响应式的,即应用 refreactive 将其包装,扔无奈实现数据响应式,其接管一个参数,即原始数据,并返回被标记后的数据

咱们来看一下代码

<template>    <p>{{ state.name }}</p>    <p>{{ state.age }}</p>    <button @click="change">扭转</button></template><script>import {reactive, markRaw} from 'vue'export default {    setup() {        const obj = {          name: '前端印象',          age: 22        }        // 通过markRaw标记原始数据obj, 使其数据更新不再被追踪        const raw = markRaw(obj)           // 试图用reactive包装raw, 使其变成响应式数据        const state = reactive(raw)            function change() {          state.age = 90          console.log(state);        }        return {state, change}    }}</script>

咱们来看一下在被 markRaw 办法解决过后的数据是否还能被 reactive 包装成响应式数据


从图中能够看到,即便咱们批改了值也不会更新视图了,即没有实现数据响应式

(11)provide && inject

与 Vue2中的 provideinject 作用雷同,只不过在Vue3中须要手动从 vue 中导入

这里简略阐明一下这两个办法的作用:

  • provide :向子组件以及子孙组件传递数据。接管两个参数,第一个参数是 key,即数据的名称;第二个参数为 value,即数据的值
  • inject :接管父组件或先人组件传递过去的数据。接管一个参数 key,即父组件或先人组件传递的数据名称

假如这有三个组件,别离是 A.vueB.vueC.vue,其中 B.vueA.vue 的子组件,C.vueB.vue 的子组件

// A.vue<script>import {provide} from 'vue'export default {    setup() {        const obj= {          name: '前端印象',          age: 22        }        // 向子组件以及子孙组件传递名为info的数据        provide('info', obj)    }}</script>// B.vue<script>import {inject} from 'vue'export default {    setup() {            // 接管A.vue传递过去的数据        inject('info')  // {name: '前端印象', age: 22}    }}</script>// C.vue<script>import {inject} from 'vue'export default {    setup() {            // 接管A.vue传递过去的数据        inject('info')  // {name: '前端印象', age: 22}    }}</script>

(12)watch && watchEffect

watchwatchEffect 都是用来监督某项数据变动从而执行指定的操作的,但用法上还是有所区别

watch:watch( source, cb, [options] )

参数阐明:

  • source:能够是表达式或函数,用于指定监听的依赖对象
  • cb:依赖对象变动后执行的回掉函数
  • options:可参数,能够配置的属性有 immediate(立刻触发回调函数)、deep(深度监听)

当监听 ref 类型时:

<script>import {ref, watch} from 'vue'export default {    setup() {            const state = ref(0)        watch(state, (newValue, oldValue) => {          console.log(`原值为${oldValue}`)          console.log(`新值为${newValue}`)          /* 1秒后打印后果:                  原值为0                  新值为1          */        })        // 1秒后将state值+1        setTimeout(() => {          state.value ++        }, 1000)    }}</script>

当监听 reactive 类型时:

<script>import {reactive, watch} from 'vue'export default {    setup() {            const state = reactive({count: 0})        watch(() => state.count, (newValue, oldValue) => {          console.log(`原值为${oldValue}`)          console.log(`新值为${newValue}`)          /* 1秒后打印后果:                  原值为0                  新值为1          */        })        // 1秒后将state.count的值+1        setTimeout(() => {          state.count ++        }, 1000)    }}</script>

当同时监听多个值时:

<script>import {reactive, watch} from 'vue'export default {    setup() {            const state = reactive({ count: 0, name: 'zs' })         watch(            [() => state.count, () => state.name],             ([newCount, newName], [oldvCount, oldvName]) => {              console.log(oldvCount) // 旧的 count 值              console.log(newCount) // 新的 count 值              console.log(oldName) // 旧的 name 值              console.log(newvName) // 新的 name 值            }          )          setTimeout(() => {            state.count ++            state.name = 'ls'          }, 1000)    }}</script>

因为 watch 办法的第一个参数咱们曾经指定了监听的对象,因而当组件初始化时,不会执行第二个参数中的回调函数,若咱们想让其初始化时就先执行一遍,能够在第三个参数对象中设置 immediate: true

watch 办法默认是渐层的监听咱们指定的数据,例如如果监听的数据有多层嵌套,深层的数据变动不会触发监听的回调,若咱们想要其对深层数据也进行监听,能够在第三个参数对象中设置 deep: true

补充: watch办法会返回一个stop办法,若想要进行监听,便可间接执行该stop函数

接下来再来聊聊 watchEffect,它与 watch 的区别次要有以下几点:

  1. 不须要手动传入依赖
  2. 每次初始化时会执行一次回调函数来主动获取依赖
  3. 无奈获取到原值,只能失去变动后的值

来看一下该办法如何应用:

<script>import {reactive, watchEffect} from 'vue'export default {    setup() {              const state = reactive({ count: 0, name: 'zs' })          watchEffect(() => {          console.log(state.count)          console.log(state.name)          /*  初始化时打印:                  0                  zs            1秒后打印:                  1                  ls          */          })          setTimeout(() => {            state.count ++            state.name = 'ls'          }, 1000)    }}</script>

从上述代码中能够看出,咱们并没有像 watch 办法一样先给其传入一个依赖,而是间接指定了一个回调函数

当组件初始化时,将该回调函数执行一次,主动获取到须要检测的数据是 state.countstate.name

依据以上特色,咱们能够自行抉择应用哪一个监听器

(13)getCurrentInstance

咱们都晓得在Vue2的任何一个组件中想要获取以后组件的实例能够通过 this 来失去,而在Vue3中咱们大量的代码都在 setup 函数中运行,并且在该函数中 this 指向的是 undefined,那么该如何获取到以后组件的实例呢?

这时能够用到另一个办法,即 getCurrentInstance

<template>    <p>{{ num }}</p></template><script>import {ref, getCurrentInstance} from 'vue'export default {    setup() {            const num = ref(3)        const instance = getCurrentInstance()        console.log(instance)        return {num}    }}</script>

咱们来看一下其打印后果


因为 instance 蕴含的内容太多,所以没截残缺,然而次要的内容都在图上了,咱们重点来看一下 ctxproxy,因为这两个才是咱们想要的 this 的内容



能够看到 ctxproxy 的内容非常相似,只是后者绝对于前者内部包装了一层 proxy,由此可阐明 proxy 是响应式的

(14)useStore

在Vue2中应用 Vuex,咱们都是通过 this.$store 来与获取到Vuex实例,但上一部分说了本来Vue2中的 this 的获取形式不一样了,并且咱们在Vue3的 getCurrentInstance().ctx 中也没有发现 $store 这个属性,那么如何获取到Vuex实例呢?这就要通过 vuex 中的一个办法了,即 useStore

// store 文件夹下的 index.jsimport Vuex from 'vuex'const store = Vuex.createStore({    state: {      name: '前端印象',      age: 22    },    mutations: {      ……    },    ……})// example.vue<script>// 从 vuex 中导入 useStore 办法import {useStore} from 'vuex'export default {    setup() {            // 获取 vuex 实例        const store = useStore()        console.log(store)    }}</script>

咱们来看一下打印后果


而后接下来就能够像之前一样失常应用 vuex

(15)获取标签元素

最初再补充一个 ref 另外的作用,那就是能够获取到标签元素或组件

在Vue2中,咱们获取元素都是通过给元素一个 ref 属性,而后通过 this.$refs.xx 来拜访的,但这在Vue3中曾经不再实用了

接下来看看Vue3中是如何获取元素的吧

<template>  <div>    <div ref="el">div元素</div>  </div></template><script>import { ref, onMounted } from 'vue'export default {  setup() {    // 创立一个DOM援用,名称必须与元素的ref属性名雷同    const el = ref(null)    // 在挂载后能力通过 el 获取到指标元素    onMounted(() => {      el.value.innerHTML = '内容被批改'    })    // 把创立的援用 return 进来    return {el}  }}</script>

获取元素的操作一共分为以下几个步骤:

  1. 先给指标元素的 ref 属性设置一个值,假如为 el
  2. 而后在 setup 函数中调用 ref 函数,值为 null,并赋值给变量 el,这里要留神,该变量名必须与咱们给元素设置的 ref 属性名雷同
  3. 把对元素的援用变量 el 返回(return)进来
补充:设置的元素援用变量只有在组件挂载后能力拜访到,因而在挂载前对元素进行操作都是有效的

当然如果咱们援用的是一个组件元素,那么取得的将是该组件的实例对象,这里就不做过多的演示了

三、结束语

本文也是笔者对Vue3的学习与了解。因为在之前学习的过程中也查阅了大量的文档资料,并一直地测试摸索,以及在Vue3我的项目中的心得体会,都让我对Vue3有了更深的意识,与此同时,我在各个社区或者是社交群里都发现很多小伙伴对Vue3的API都不太熟悉,甚至不晓得有这些API,所以我就写下了这篇总结文章,将我所晓得、所了解的都分享给大家

欢送关注公众号:前端印象,一起交换分享前端技术常识