Vue3新特性适合新手

592 阅读5分钟

1 创建一个vue3+ts项目

main.ts

//程序的主入口问卷,ts文件
import { createApp } from 'vue'
import App from './App.vue'

createApp(App).mount('#app')

App.vue

<template>
<!--  vue2 中的html模板需要根标签,vue3可以不需要-->
  <img alt="Vue logo" src="./assets/logo.png">
<!--  4.使用子组件-->
  <HelloWorld msg="Welcome to Your Vue.js + TypeScript App"/>
</template>


<script lang="ts">
    //<!--以下使用ts代码-->
    
//  1  defineComponent函数,定义一个组件,内部可以传入一个配置对象
import { defineComponent } from 'vue';
//2 引入一个子组件
import HelloWorld from './components/HelloWorld.vue';

//3 暴露出去一个定义好的组件
export default defineComponent({
  // 3.1 当前组件的名字是App
  name: 'App',
  // 3.2 注册组件
  components: {
    // 3.3 注册一个子组件
    HelloWorld
  }
});
</script>

<style>
#app {
  font-family: Avenir, Helvetica, Arial, sans-serif;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-align: center;
  color: #2c3e50;
  margin-top: 60px;
}
</style>

2 Composition API

setup

  • 新的 option, 所有的组合 API 函数都在此使用, 只在初始化时执行一次
  • 函数如果返回对象, 对象中的属性或方法, 模板中可以直接使用
<template>
<div>good day</div>
  <h2>{{number}}</h2>
</template>


<script lang="ts">


//  1  defineComponent函数,定义一个组件,内部可以传入一个配置对象
import { defineComponent } from 'vue';

//3 暴露出去一个定义好的组件
export default defineComponent({
  // 3.1 当前组件的名字是App
  name: 'App',
    //测试代码,setup是组合API中第一个要使用的函数
    setup(){
      const number = 10
        return{
          number
        }
    }

});
</script>

setup 与ref是基本使用

  • 需求:页面打开后看到数据,并且可以点击按钮更新数据
<template>
<h2>setup 与ref是基本使用</h2>
  <h3>{{count}}</h3>
  <button @click="updateCount">update count</button>
</template>


<script lang="ts">

import { ref,defineComponent } from 'vue';
export default defineComponent({
 name: 'App',
//需求:页面打开后看到数据,并且可以点击按钮更新数据
//    vue2 方法
    data(){
     return{
      count:0//属性
     }
    },
    methods:{
        updateCount(){//方法
            this.count++
        }
    },

//  3  vue3方法
//3.1setup 是组合API的入口函数
    setup(){
     //3.3 变量
     // let count = 0//此时的数据不是响应式的数据
     //   ref是一个函数,用于:定义一个响应式数据,返回一个ref对象,对象中有value属性,如果需要对数据进行操作,需要对对象.value进行操作
     // ref 一般是用来定义一个基本类型的响应式数据
        const count = ref(0)
     //   3.4 方法
       function updateCount(){
         // count++//报错的原因: count是一个ref对象,对象是不能进行++操作
           count.value++
       }
     //3.2返回的是一个对象
     return{
         count, //属性
         updateCount //方法
     }
    }
});
</script>

reactive 的使用

-   作用: 定义多个数据的响应式
-   const proxy = reactive(obj): 接收一个普通对象然后返回该普通对象的响应式代理器对象
-   响应式转换是“深层的”:会影响对象内部所有嵌套的属性
-   内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据都是响应式的
  • 需求显示用户的相关信息,点击按钮,跟新用户的相关信息
<template>
<h2>reactive 基本使用</h2>
<h3>name:{{user.name}}</h3>
  <h3>age:{{user.age}}</h3>
  <h3>friend:{{user.friend}}</h3>
  <br>
  <button @click="updataUser">updata</button>
</template>


<script lang="ts">

import { reactive,defineComponent } from 'vue';
export default defineComponent({
 name: 'App',
  setup(){
     //1 把数据变成响应式的
     const obj = {
         name:'tom',
         age:10,
         friend:{
             name:'lily',
             age:12,
             books:['a','b','c']
         }
     }
     const user = reactive(obj)
      //返回的是一个Proxy的代理对象,被代理的目标对象就是reactive的对象
      //user 是代理对象,obj是目标对象
      console.log(user)//Proxy {name: 'tom', age: 10, friend: {…}}[[Handler]]: Object[[Target]]: Object[[IsRevoked]]: false


      //3 方法
      const updataUser =()=>{
          user.name ='lone'
          user.age ++
          user.friend.name = 'luca'
      }
      // 2 return
      return{
          user,
          updataUser
      }
  }
});
</script>

reactive 与 ref-细节

  • 是 Vue3 的 composition API 中 2 个最重要的响应式 API
  • ref 用来处理基本类型数据, reactive 用来处理对象(递归深度响应式)
  • 如果用 ref 对象/数组, 内部会自动将对象/数组转换为 reactive 的代理对象
  • ref 内部: 通过给 value 属性添加 getter/setter 来实现对数据的劫持
  • reactive 内部: 通过使用 Proxy 来实现对对象内部所有数据的劫持, 并通过 Reflect 操作对象内部数据
  • ref 的数据操作: 在 js 中要.value, 在模板中不需要(内部解析模板时会自动添加.value)

toRefs

把一个响应式对象转换成普通对象,该普通对象的每个 property 都是一个 ref

应用: 当从合成函数返回响应式对象时,toRefs 非常有用,这样消费组件就可以在不丢失响应式的情况下对返回的对象进行分解使用

问题: reactive 对象取出的所有属性值都是非响应式的

解决: 利用 toRefs 可以将一个响应式 reactive 对象的所有原始属性转换为响应式的 ref 属性

computed \watch \watchEffect

  • computed 函数:

    • 与 computed 配置功能一致
    • 只有 getter
    • 有 getter 和 setter
  • watch 函数

    • 与 watch 配置功能一致
    • 监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
    • 默认初始时不执行回调, 但可以通过配置 immediate 为 true, 来指定初始时立即执行第一次
    • 通过配置 deep 为 true, 来指定深度监视
  • watchEffect 函数

    • 不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
    • 默认初始时就会执行第一次, 从而可以收集需要监视的数据
    • 监视数据发生变化时回调
<template>
  <h2>App</h2>
  fistName:
  <input v-model="user.firstName" />
  <br />
  lastName:
  <input v-model="user.lastName" />
  <br />
  fullName1:
  <input v-model="fullName1" />
  <br />
  fullName2:
  <input v-model="fullName2" />
  <br />
  fullName3:
  <input v-model="fullName3" />
  <br />
</template>

<script lang="ts">
/*
计算属性与监视
1. computed函数:
  与computed配置功能一致
  只有getter
  有getter和setter
2. watch函数
  与watch配置功能一致
  监视指定的一个或多个响应式数据, 一旦数据变化, 就自动执行监视回调
  默认初始时不执行回调, 但可以通过配置immediate为true, 来指定初始时立即执行第一次
  通过配置deep为true, 来指定深度监视
3. watchEffect函数
  不用直接指定要监视的数据, 回调函数中使用的哪些响应式数据就监视哪些响应式数据
  默认初始时就会执行第一次, 从而可以收集需要监视的数据
  监视数据发生变化时回调
*/

import { reactive, ref, computed, watch, watchEffect } from 'vue'

export default {
  setup() {
    const user = reactive({
      firstName: 'A',
      lastName: 'B'
    })

    // 只有getter的计算属性
    const fullName1 = computed(() => {
      console.log('fullName1')
      return user.firstName + '-' + user.lastName
    })

    // 有getter与setter的计算属性
    const fullName2 = computed({
      get() {
        console.log('fullName2 get')
        return user.firstName + '-' + user.lastName
      },

      set(value: string) {
        console.log('fullName2 set')
        const names = value.split('-')
        user.firstName = names[0]
        user.lastName = names[1]
      }
    })

    const fullName3 = ref('')

    /*
    watchEffect: 监视所有回调中使用的数据
    */
    /*
    watchEffect(() => {
      console.log('watchEffect')
      fullName3.value = user.firstName + '-' + user.lastName
    })
    */

    /*
    使用watch的2个特性:
      深度监视
      初始化立即执行
    */
    watch(
      user,
      () => {
        fullName3.value = user.firstName + '-' + user.lastName
      },
      {
        immediate: true, // 是否初始化立即执行一次, 默认是false
        deep: true // 是否是深度监视, 默认是false
      }
    )

    /*
    watch一个数据
      默认在数据发生改变时执行回调
    */
    watch(fullName3, value => {
      console.log('watch')
      const names = value.split('-')
      user.firstName = names[0]
      user.lastName = names[1]
    })

    /*
    watch多个数据:
      使用数组来指定
      如果是ref对象, 直接指定
      如果是reactive对象中的属性,  必须通过函数来指定
    */
    watch([() => user.firstName, () => user.lastName, fullName3], values => {
      console.log('监视多个数据', values)
    })

    return {
      user,
      fullName1,
      fullName2,
      fullName3
    }
  }
}
</script>

3 vue2 (option API)和 vue3 (composition API)区别

传统的option API中新增或者修改一个需求,就需要分别在data,methods.computed,watched 中修改,滚动条反复移动 composition API中就更优雅,每个需求放在一起写,相关功能的代码结构更加有序。 截屏2021-09-28 13.40.24.png

4 比较 Vue2 与 Vue3 的响应式(重要)

vue2 的响应式

  • 核心:

    • 对象: 通过 defineProperty 对对象的已有属性值的读取和修改进行劫持(监视/拦截)
    • 数组: 通过重写数组更新数组一系列更新元素的方法来实现元素修改的劫持
Object.defineProperty(data, 'count', {
  get() {},
  set() {}
})
  • 问题

    • 对象直接新添加的属性或删除已有属性, 界面不会自动更新
    • 直接通过下标替换元素或更新 length, 界面不会自动更新 arr[1] = {}

Vue3 的响应式

new Proxy(data, {
  // 拦截读取属性值
  get(target, prop) {
    return Reflect.get(target, prop)
  },
  // 拦截设置属性值或添加新属性
  set(target, prop, value) {
    return Reflect.set(target, prop, value)
  },
  // 拦截删除属性
  deleteProperty(target, prop) {
    return Reflect.deleteProperty(target, prop)
  }
})

proxy.name = 'tom'
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Proxy 与 Reflect</title>
  </head>
  <body>
    <script>
      const user = {
        name: 'John',
        age: 12
      }

      /*
    proxyUser是代理对象, user是被代理对象
    后面所有的操作都是通过代理对象来操作被代理对象内部属性
    */
      const proxyUser = new Proxy(user, {
        get(target, prop) {
          console.log('劫持get()', prop)
          return Reflect.get(target, prop)
        },

        set(target, prop, val) {
          console.log('劫持set()', prop, val)
          return Reflect.set(target, prop, val) // (2)
        },

        deleteProperty(target, prop) {
          console.log('劫持delete属性', prop)
          return Reflect.deleteProperty(target, prop)
        }
      })
      // 读取属性值
      console.log(proxyUser === user)
      console.log(proxyUser.name, proxyUser.age)
      // 设置属性值
      proxyUser.name = 'bob'
      proxyUser.age = 13
      console.log(user)
      // 添加属性
      proxyUser.sex = '男'
      console.log(user)
      // 删除属性
      delete proxyUser.sex
      console.log(user)
    </script>
  </body>
</html>

6 数据绑定(双向、单向)

  1. Vue
    1. data中初始化数据
    2. 修改数据: this.key = value
    3. 数据流:
      1. Vue是单项数据流: Model ---> View

      2. Vue中实现了双向数据绑定: v-model

截屏2021-09-28 14.09.38.png

  1. 小程序
    1. data中初始化数据
    2. 修改数据: this.setData()
      1. 修改数据的行为始终是同步的
    3. 数据流:
      1. 单项: Model ---> View
  2. React
    1. state中初始化状态数据
    2. 修改数据: this.setState()
      1. 自身钩子函数中(componentDidMount)异步的
      2. 非自身的钩子函数中(定时器的回调)同步的
    3. 数据流:
      1. 单项: Model ---> View

7 vue2 和vue3 生命周期

vue2 生命周期的钩子函数

在谈到Vue的生命周期的时候,

先需要创建一个实例,也就是在 new Vue ( ) 的对象过程当中,首先执行了init,调用了beforeCreate

然后在injections(注射)和reactivity(反应性)的时候,它会再去调用 created

当created完成之后,它会去判断 实例 里面是否含有“el”option(选项),如果没有的话,它会调用vm.$mount(el)这个方法,然后执行下一步;如果有的话,直接执行下一步。

紧接着会判断是否含有“template”这个选项,如果有的话,它会把template解析成一个 render function ,render函数返回一个createElement方法,render函数是发生在beforeMount和mounted之间的,

beforeMount在有了render function的时候才会执行,当执行完render function之后,就会调用mounted这个钩子,在mounted挂载完毕之后,这个实例就算是走完流程了。

后续的钩子函数执行的过程都是需要外部的触发才会执行:

比如说有数据的变化,会调用beforeUpdate

然后经过Virtual DOM,最后updated更新完毕。

当组件被销毁的时候,它会调用beforeDestory,以及destoryed

vue3 的生命周期

与 2.x 版本生命周期相对应的组合式 API

  • beforeCreate -> 使用 setup()
  • created -> 使用 setup()
  • beforeMount -> onBeforeMount
  • mounted -> onMounted
  • beforeUpdate -> onBeforeUpdate
  • updated -> onUpdated
  • beforeDestroy -> onBeforeUnmount
  • destroyed -> onUnmounted
  • errorCaptured -> onErrorCaptured