Vue3.0 笔记

234 阅读12分钟

1. Vue3简介

  • 2020年9月18日,Vue.js 发布3.0版本,代号: One Piece(海贼王)
  • 耗时2年多、2600+次提交、30+个RFC(请求修改意见稿)、600+次PR(拉取请求)、99位贡献者
  • tags地址: github.com/vuejs/vue-n…

2. Vue3带来了什么

2.1 性能的提升

  • 打包大小减少41%
  • 初次渲染快555,更新渲染快133%
  • 内存减少54%
  • ...

2.2 源码的升级

  • 使用Proxy代替defineProperty实现响应式
  • 重写虚拟DOm的实现和Tree-Shaking
  • ...

2.3 拥抱TypeScript

  • vue3可以更好的支持TypeScript

2.4 新的特性

  1. Composition API (组合API)

    setup配置

    ref与reactive

    watch与watchEffect

    provide与inject

    ....

  2. 新的内置组件

    Fragment

    Teleport

    Suspense

    ...

  3. 其他改变

    新的生命周期钩子

    data选项应始终被声明为一个函数

    移除keyCode支持作为v-on的修饰符

    ...

3. 创建Vue3.0工程

1. 使用vue-cli创建

// vue -V 版本号要大于4.5.0
npm i -g @vue/cli 
vue create <project-name>
cd <project-name>
npm run serve

2. 使用Vite创建

vite 新一代的前端构建工具

优势:

  • 开发环境中,无需打包操作,可快速的冷启动
  • 轻量快速的热重载HMR
  • 真正的按需编译,不再等待整个应用编译完成。
## 创建工程
  npm init vite-app <project-name>
## 进入工程目录
  cd <project-name>
## 安装依赖
   npm install
## 运行
   npm run dev
 

4. 常用的Composition API

组合式API

4.1 setup

  1. setup: Vue3.0中一个新的配置项,值为一个函数

  2. setup 是所有Composition API 表演的舞台。

  3. 组件中所用到的: 数据、方法等等 ,均要配置在setup中。

  4. setup函数的两种返回值: (1)若返回一个对象,则对象中的属性、方法,在模板中均可以直接使用!!!!(重点

    export default {
        name: 'App',
        // 此处暂时不考虑响应式数据
        setup() {
            //数据
            let name ='糖糖糖'
            let age  = 22 
            // 方法
            function sayHello() {
                console.log(`你好啊,我叫${name},我${age}岁了`)
            }
            return {
              name,
              age,
              sayHello
            }
        }
    }
    
    

    (2)若返回一个渲染函数,则可以自定义渲染内容(了解

      import {h} from 'vue'
       export default {
           name: 'App',
           setup() {
              ...
            // 返回一个函数 我们称之为渲染函数
            return  () => h('h1','tanttangtang')
           }
       }
       
      }
    

注意: vue3 可以向下兼容vue2的写法

  1. 注意点 (1)尽量不要与Vue2.x配置混用
    • Vue2.x配置(data、methods、computed.....)中可以访问到setup中的属性、方法。
    • 但在setup中不能访问到Vue2.x配置(data、methods、computed.....)
    • 如果有重名,setup优先。 (2)setup不能是一个async函数,因为返回值不再是return的对象,而是promise,模板看不到return对象中的属性。(后期也可以返回一个Promise实例,但需要Suspense和异步组件的配合)

4.2 ref函数

ref函数的作用是定义一个响应式的数据

语法:const xxx = ref(iinitValue)

  • 创建一个包含响应式数据的引用对象(reference对象简称ref对象)
  • jS中操作数据: xxx.value
  • 模板中读取数据: 不需要.value ,直接<div>{{xxx}}</div> 备注:
  • 接收的数据可以是: 基本类型、也可以是对象类型。
  • 基本类型的数据: 响应式依然是靠Object.defineProperty()的get与set完成的。
  • 对象类型的数据: 内部求助了vue3.0中的一个新函数————reactive函数
<h1>{{name}}</h1>
<h2>{{age}}</h2> // 注意不用name.value age.value 
import { ref } from 'vue' 
export default {
  name: 'App',
  setup() {
    // 数据
    let name = ref("张三")
    let age = ref(18)
   // 方法
   function changeInfo(){
     name.value = "李四" // 注意这里需要用.value
     age.value = 48
   }
    // 返回一个对象(常用)
    return {
      name,
      age,
      changeInfo
    }
  }
}

用ref包装完的数据,是RefImpl的实例对象,叫引用实现的实例简称引用对象

RefImpl = > reference 引用, implement 实现

以下是ref函数 升级版本

<h1>{{name}}</h1>
<h2>{{age}}</h2> // 注意不用name.value age.value 
<h2>{{job.type}}</h2>
<h2>{{job.salary}}</h2>
import {ref} from 'vue' 
export default {
  name: 'App',
  setup() {
    // 数据
    let name = ref("张三") 
    let age = ref(18) 
    let job = ref({ 
      type: '前端工程师',
      salary: '30k'
    })
   // 方法
   function changeInfo(){
     name.value = "李四"
     age.value = 48
     console.log(job.value)// proxy { type: "前端工程师", salary: "30k"}
     job.value.type = "UI设计师"
     job.value.salary = '60k'
   }
    // 返回一个对象(常用)
    return {
      name,
      age,
      job,
      changeInfo
    }
  }
}

4.3 reactive函数

作用: 定义一个对象类型的响应式数据(基本类型不要用它,要用ref函数)

语法: const 代理对象 = reactive(源对象)接收一个对象(或数组),返回一个代理对象(Proxy的实例对象简称proxy对象

  • reactive定义的响应式数据是深层次的
  • 内部基于Es6的Proxy实现,通过代理对象操作源对象内部数据进行操作。
import { ref, reactive } from 'vue'

export default {
  name: 'App',
  setup() {
    // 数据
    let name = ref("张三") 
    let age = ref(18) 
    let job = reactive({  
      type: '前端工程师',
      salary: '30k'
    })
   // 方法
   function changeInfo(){
     name.value = "李四"
     age.value = 48
     console.log(job) // proxy { type: "前端工程师", salary: "30k"}
     job.type = "UI设计师"
     job.salary = '60k'
   }
    // 返回一个对象(常用)
    return {
      name,
      age,
      job,
      changeInfo
    }
  }
}

// ------------------------------
import { reactive} from 'vue'

export default {
  name: 'App',
  setup() {
    // 数据
    let person = reactive({  
      name: '张三',
      age: 18,
      job: {
        type: '前端工程师',
         salary: '30k',
         a: {
           b: {
             c: 666
           }
         }
      },
      hobby: ['抽烟','喝酒','烫头'] 
    })
   // 方法
   function changeInfo(){
      person.name = "李四"
      person.age = 48
      person.job.type = "UI设计师"
      person.job.salary = '60k'
      person.job.a.b.c = 999
      person.hobby[0] = '学习' // vue3里面可以直接通过索引修改数组数据
   }
    // 返回一个对象(常用)
    return {
     person,
      changeInfo
    }
  }
}

html: {{person.name}} {{person.age}}  {{person.job.type} {{person.job.a.b.c}}}

4.4 vue2.0 与 vue3.0 的响应式

Vue2.x的响应式 :

实现原理

  • 对象类型:通过Object.defineProperty()对属性的读取、修改进行拦截(数据劫持)
  • 数组类型: 通过重写数组的一些列方法来实现拦截。 (对数组的变更方法进行了包裹)
  Object.defineProperty(data,'count',{
    get(){},
    set(){}
  })

存在问题

  • 新增属性、删除属性,界面不会更新

  • 直接通过下标修改数组,界面不会自动更新。

    this.$set(this.person,'sex','女')
    等同于
    Vue.set(this.person,'sex','女')
    
    this.$delete(this.person,'name')
      等同于
    Vue.delete(this.person,'name','女')
    
    
     this.person.hobby.splice(0,1,'逛街')
    

Vue3.0的响应式:

实现原理

  • 通过Proxy(代理): 拦截对象中任意属性的变化,包括: 属性值的读写、属性的添加、属性的删除等。
  • 通过Reflect(反射):对代理对象的属性进行操作。 Reflect是window上的方法
  • MDN文档中描述的Proxy与reflect
 let a = {a:1 ,b:2}
 Reflect.get(obj,'a') // 读
 Reflect.set(obj,'a',66666)// 改
 Reflect.deleteProperty(obj,'a')// 删除
 
//#region  
//#endregion

// !!!!!!!模拟vue3中实现响应式!!!!!!!!!
const p = new Proxy(person,{
 // 有人读取p的某个属性时调用
  get(target,propName) { // target源对象这里指person  propName属性名
       console.log(`有人读取了p身上的${propName}属性`)
       return Reflect.get(target,propName)
     },
  //有人修改p的某个属性、或给p追加某个属性时调用
   set(target,propName,value) {
       console.log(`有人修改了p身上的${propName}属性,我要去更新界面了`)
       return Reflect.set(target,propName,value)
 },
  //有人删除p的某个属性时调用
   deleteProperty(target,propName) {
       console.log(`有人删除了p身上的${propName}属性,我要去更新界面了`)
       return Reflect.deleteProperty(target,propName)
 }
})

4.5 reactive对比ref

一: 从定义数据角度对比:

  • ref用来定义: 基本类型数据
  • reactive用来定义: 对象(或数组)类型数据
  • 备注: ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象。 二: 从原理角度对比:
  • ref通过Object.defineProperty()getset来实现响应式(数据劫持)。
  • reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据。 三: 从使用角度对比:
  • ref定义的数据: 操作数据需要.value ,读取数据时模板中直接读取不需要.value 。
  • reactive定义的数据: 操作数据与读取数据: 均不需要.value 。
//写成一个对象
let data = reactive({
  person: {},
  user: {}
})
return {
  data 
}

4.6 setup的两个注意点

  1. setup执行的时机 在beforeCreate之前执行一次,this是undefined

  2. setup的参数

    • props: 值为对象,包含: 组件外部传递过来,且组件内部声明接收了的属性。
    • context: 上下文对象 attrs: 值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性,相当于this.$attrs

    slots: 收到的插槽内容,相当于this.$slots

    emit: 分发自定义事件的函数,相当于this.$emit

     export default {
       name: 'Demo',
       beforeCreate() {
         console.log('-------default-----')
       },
       props: ['msg','school'], // vue2和vue3都支持这个配置,告诉vue 我要接收msg ,school 
       emits: ['hello'], //vue3里面必须要写!!!,告诉它我知道了我要触发hello事件 
       setup(props,context) {  // this是unfined,所以setup里面是不会出现this 
         console.log('--------setup----------',this,props,context)
         console.log(context.attrs) // 相当于vue2中的$attrs 
         console.log(context.slots) //插槽
         console.log(context.emit) //触发自定义事件
         // 数据
         let person = reactive({
           name: '张三',
           age: 18
         })
         // 方法
         function test() {
           context.emit('hello',6666) // hello是事件,6666是值 
         }
         // 返回一个对象(常用)
         return {
           person,
           test
         }
       }
     }
     
    

setup能接收两个参数,第一个是props , 第二个是context

给组件绑定的事件是自定义事件, 如果非要绑定原生事件比如click需要加.native 。 @click.native='add'

<template slot="absc"></template> 第二种写法 <template v-slot:absc></template> (vue3推荐这种写法)

4.7 计算属性与监视

  1. computed 函数

    与Vue2.x中computed配置功能一致

    写法一: 
    import { reactive, computed } from 'vue' //  因为计算属性变成了可组合式的api可以必须要引入
    setup() {
      ...
      // 数据
      let person = reactive({
        firstName: '张',
        lastName: '三'
      })
      //计算属性——————简写(没有考虑计算属性被修改的情况)
      let fullName = computed(() => {
        return person.firstName + '-' + person.lastName
      })
      // 计算属性 —————— 完整(考虑读和写)
       let fullName = computed({ // 传入一个对象
        get() {
          return person.firstName + '-' + person.lastName
        },
        set(value) {
          const nameArr = value.split('-')
          person.firstName = nameArr[0]
          person.lastName = nameArr[1]
        }
      })
      return {
        person,
        fullName
      }
    }
    
    
    写法二: 
    import { reactive, computed } from 'vue' //  因为计算属性变成了可组合式的api可以必须要引入
    setup() {
      ...
      // 数据
      let person = reactive({
        firstName: '张',
        lastName: '三'
      })
      //计算属性——————简写(没有考虑计算属性被修改的情况)
      person.fullName = computed(() => {
        return person.firstName + '-' + person.lastName
      })
      // 计算属性 —————— 完整(考虑读和写)
       person.fullName = computed({ // 传入一个对象
        get() {
          return person.firstName + '-' + person.lastName
        },
        set(value) {
          const nameArr = value.split('-')
          person.firstName = nameArr[0]
          person.lastName = nameArr[1]
        }
      })
      return {
        person
      }
    }
    
    
  2. watch 函数

    与vue2.x中watch配置功能一致

    两个小坑:

    • 监视reactive定义的响应式数据时:oldValue无法正确获取,强制开启了深度监视(deep配置失效)。

    • 监视reactive定义的响应式数据中某个属性时: deep配置有效。

  写法: 
  import { ref,watch } from 'vue' 
  // 数据
  let sum = ref(0)
  let msg = ref('你好啊')
  情况一: 监视ref定义的一个响应式数据
  watch(sum,(newValue,oldValue) => { 
    console.log(`sum变化了`,newValue,oldValue)
  },{immediate: true})
  情况二: 监视多个ref定义的响应式数据
  watch([sum,msg],(newValue,oldValue) => {
    console.log(`sum或msg变化了`,newValue,oldValue)
  }) 

 import {reactive} from 'vue' 
  情况三: 监视reactive定义的响应式数据
  若watch监视的是reactive定义的响应式数据,则无法正确获取oldValue!!
  若watch监视的是reactive定义的响应式数据,则强制开启了深度监视

  watch(person,(newValue,oldValue) => {
    console.log(`person变化了`,newValue,oldValue)
  },{ immediate: true,deep: false }) // 此处的deep配置无效
  
  情况四: 监视reactive定义的响应式数据中的某个属性
  watch(() => person.job,(newValue,oldValue) => {  // 写成一个函数,返回你想监听属性
    console.log(`person的job变化了`,newValue,oldValue)
  },{immediate: true,deep: true})


情况五: 监视reactive定义的响应式数据中的某些属性
  watch([() => person.job,()=>person.name],(newValue,oldValue) => { 
    console.log(`person的job或name变化了`,newValue,oldValue)
  },{immediate: true,deep: true})

// 特殊情况
person.job  = {
  j1: {
    salary: 20 
  }
}
 watch(() => person.job,(newValue,oldValue) => {  // 写成一个函数,返回你想监听属性
    console.log(`person的job变化了`,newValue,oldValue)
  },{immediate: true,deep: true}) // 此处由于监视的是reactvie所定义的对象中的某个属性,所以deep配置有效。 
vue2.x 写法 
watch:{
  sum(newValue,oldValue){ 
    console.log('sum的值变化了',newValue,oldValue)
  },
  // 完整写法
  sum : {
    immediate: true,
    deep: true,
    handler(newValue,oldValue) {
      console.log('sum的值变化了',newValue,oldValue)
    }
  }
}

watchEffect函数

  • watch的套路是: 既要指明监视的属性,也要指明监视的回调。
  • watchEffect的套路是: 不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性。
  • watchEffect有点像computed:
    1. 但computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值。
    2. 而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值。

watchEffect所指定的回调中用到的数据只要发生变化,则直接重新执行回调。

import { ref,reactive,watchEffect } from 'vue'
export default {
  name: 'Demo',
  setup(){
    // 数据
    let sum = ref(0)
    let person = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })
    //
    watchEffect(()=>{ // 会看{}里面用到了谁,它就监视谁,特别智能. 我不说我监视谁,你用谁我就监视谁
      const x1 = sum.value
      const v2 = person.age
      const x2 = person.job.j1.salary 
      console.log(`watchEffect配置的回调执行了`)
    })
    return {
      sum,
      person
    }
  }
}

4.8 vue3 生命周期

把vue2里面的beforeDestroy 换成 beforeUnmount ,destroyed 换成 unmounted

//vue3.0 生命周期

export default {
  name: 'App',
  components: {Demo},
  setup() {
    let isShowDemo = ref(true)
    return {
      isShowDemo
    }
  },
  // 通过配置项的形式使用生命周期钩子
  beforeCreate() {
    console.log('---beforeCreate----')
  },
  created() {
    console.log('---created----')
  },
  beforeMount() {
    console.log('---beforeMount----')
  },
  mounted() {
    console.log('---mounted----')
  },
  beforeUpdate() {
    console.log('---beforeUpdate----')
  },
  updated() {
    console.log('---updated----')
  },
  beforeUnmount() {
    console.log('---beforeUnmount----')
  },
  unmounted() {
    console.log('---unmounted----')
  }
}

因为Vue3.0 也提供了Composition API 形式的生命周期钩子,与Vue2.x中钩子对应关系如下:

  • beforeCreate ==> setup()
  • created ====> setup()
  • beforeMount ===> onBeforeMount
  • mounted ====> onMounted
  • beforeUpdate ===> onBeforeUpdate
  • updated ===> onUpdated
  • beforeUnmount ===> onBeforeUnmount
  • unmounted ===> onUnmounted 以上改为下面的:

//通过组合式API的形式去使用生命周期钩子

import {ref ,onBeforeMount,onMounted,onBeforeUpdate, onUpdated,onBeforeUnmount,onUnmounted } from 'vue'
export default {
  name: 'App',
  components: {Demo},
  setup() {
    // 数据
    let isShowDemo = ref(true)
    //通过组合式API的形式去使用生命周期钩子
  
      // beforeCreate created 相当于setup () 所以不能放在这里面
      onBeforeMount(() => {
        console.log('-----onBeforeMount-----')
      })
        onMounted(() => {
        console.log('-----onMounted-----')
      })
        onBeforeUpdate(() => {
        console.log('-----onBeforeUpdate-----')
      })
        onUpdated(() => {
        console.log('-----onUpdated-----')
      })
        onBeforeUnmount(() => {
        console.log('-----onBeforeUnmount-----')
      })
        onUnmounted(() => {
        console.log('-----onUnmounted-----')
      })
    //返回一个对象(常用)
    return {
      isShowDemo
    }
  },
  
}

4.9 自定义hook函数

什么是hook ? 本质是一个函数,把setup函数中使用的Composition API进行了封装。 类似于vue2.x中的mixin 自定义hook的优势: 复用代码,让setup中的逻辑更清楚易懂。

import {ref ,reactive,onMounted,onBeforeUnmount } from 'vue'
export defalut {
  name: 'Demo',
  setup() {
    // 数据
    let sum = ref(0)
    let point = reactive({
      x: 0,
      y: 0
    })
    // 方法
    function savePoint(event) {
      point.x = event.pageX 
      point.y = event.pageY
    }
    // 生命周期钩子
    onMounted(()=>{
      window.addEventListener('click',savePoint)
    })
    onBeforeUnmount(()=>{
      window.removeEventListener('click',savePoint)
    })
    // 返回一个对象(常用)
    return {
      sum,
      point
    }
  }
}

下面用hooks 定义一个hooks文件夹 , 在里面usePoint.js

hooks文件夹下的文件一般命名以use开头

import {reactive,onMounted,onBeforeUnmount } from 'vue'
function savePoint() {
  //实现鼠标打点相关的数据
   let point = reactive({
      x: 0,
      y: 0
    })
    // 实现鼠标打点相关的方法
    function savePoint(event) {
      point.x = event.pageX 
      point.y = event.pageY
    }
    // 实现鼠标打点相关的生命周期钩子
    onMounted(()=>{
      window.addEventListener('click',savePoint)
    })
    onBeforeUnmount(()=>{
      window.removeEventListener('click',savePoint)
    })
     return point
}

// 暴露出去
export default savePoint 

或者

import {reactive,onMounted,onBeforeUnmount } from 'vue'
export default function() {
  //实现鼠标打点相关的数据
   let point = reactive({
      x: 0,
      y: 0
    })
    // 实现鼠标打点相关的方法
    function savePoint(event) {
      point.x = event.pageX 
      point.y = event.pageY
    }
    // 实现鼠标打点相关的生命周期钩子
    onMounted(()=>{
      window.addEventListener('click',savePoint)
    })
    onBeforeUnmount(()=>{
      window.removeEventListener('click',savePoint)
    })
    return point
}

在其他页面 引入

import {ref} from 'vue'
import usePoint from '../hooks/usePoint'
export defalut {
  name: 'Demo',
  setup() {
    // 数据
    let sum = ref(0)
    let point = usePoint()
    // 返回一个对象(常用)
    return {
      sum,
      point
    }
  }
}

总之: ref , reactive ,计算属性,监听属性,生命周期等等 ,只要在setup里面写的都是Composition API 组合式API。hooks就是把这些组合式API进行了封装

4.10 toRef 与 toRefs

toRef : 作用: 创建一个ref对象,其value值指向另一个对象中的某个属性。

语法: const name = toRef(person,'name') (想要person对象里面的name属性)

应用: 要将响应式对象中的某个属性单独提供弄个给外部使用时。

扩展:toRefs 与 toRef功能一致,但可以批量创建多个ref对象,语法: toRefs(person)

返回值是RefImpl实例对象

总结:toRef只能给你处理一个对象中的某个属性,toRefs能批量处理一个对象中的所有属性

// vue3里面实现对象响应式类型用的proxy
let p = new Proxy(person,{
  set(target,propName,value){
    console.log(`${propName}被修改了,我要去更新界面了`)
    Reflect.set(target,propName,value)
  }
})

解决方案一: 
import {reactive,toRef} from 'vue' 
// toRef帮你把一个不是ref的东西变成ref
export defalut {
  name:'Demo',
  setup() {
    /// 数据
    let person  = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })
    //const name1 = toReft(person,'name') // 想要person对象里面的name属性,并把它变成ref 
    //  name1 分两步: 第一步转成RefImpl{...value} 间接找的就是person.name ,通过getter引用了一下,改的就是person里面的name。 
    //返回一个对象(常用)
    return {
      name: toRef(person,'name') // 如果这里写ref(person.name) 这里改的不是person里面的name ,改的是ref(person.name)的值,数据就分家了。。。
      age: toRef(person,'age') // 这里不能写ref(person.age) // 不然原数据都不会变。 
      salary: toRef(person.job.j1,'salary') // 第一个参数传一个对象 // 这里也不能写ref(person.job.j1.salary)原因和上面一样
    }
  }
}

解决方案二: 
页面: 姓名: {{name }} {{age}} {{job.j1.salary}}
import {reactive,toRefs} from 'vue' 
//  toRef帮你把一个不是ref的东西变成ref
export defalut {
  name:'Demo',
  setup() {
    /// 数据
    let person  = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })
    // toRef只能给你处理一个对象中的某个属性,toRefs能批量处理一个对象中的所有属性
     //const x = toRefs(person)
     //console.log(x) // x是新的对象,里面的属性都是一个ref响应式对象
    //返回一个对象(常用)
    return {
      person,
      ...toRefs(person)
    }
  }
}
// toRefs(person)返回值是一个对象,所以要用...toRefs(person)
//总结修改数据的时候,原数据也会跟着变。 

5. 其他Composition API

5.1 shallowReactive 与 shallowRef

  • shallowReactive: 只处理对象最外层属性的响应式(浅响应式)

  • shallowRef: 只处理基本数据类型的响应式,不进行对象的响应式处理。 什么时候使用?

如果有一个对象数据 ,结构比较深,但变化时只是外层属性变化===shallowReactive

如果有一个对象数据,后续功能不会修改该对象中的属性,而是生新的对象来替换 ===shallowRef

页面: 姓名: {{name }} {{age}} {{job.j1.salary}}
import { reactive,shallowReactive,shallowRef } from 'vue' 
// toRef帮你把一个不是ref的东西变成ref  
export defalut {
  name:'Demo',
  setup() {
    /// 数据
    //let person  = shallowReactive({ 
    // shallowReactive 浅层次的响应式,只考虑对象类型中的第一层的响应式
    // reactive会遍历所有层次的对象变成响应式
    let person  = reactive({
      name: '张三',
      age: 18,
      job: {
        j1: {
          salary: 20
        }
      }
    })
    // let x = ref(0)  
    // let x= shallowRe(0)
    let x = shallowRef({
        y: 0 
    }) 
    let x = ref({
        y: 0 
    }) 
   //  shallowRef不去处理对象类型的响应式
   // ref可以处理基础类型的响应式,也可以处理对象类型的响应式
  
    //返回一个对象(常用)
    return {
      person,
      ...toRefs(person)
    }
  }
}

5.2 readonly 与 shallowReadonly

readonly :让一个响应式数据变为只读的(深只读)。

shallowReadonly: 让一个响应式数据变为只读的(浅只读)。

应用场景:: 不希望数据被修改时。

import {ref,reactive,toRefs,readonly,shallowReadonly} from 'vue'
export default {
    name: 'Demo',
    setup(){
        // 数据
        let sum = ref(0)
        let person = reactive({
            name: '糖糖糖',
            age: 22, 
            job: {
                j1: {
                    salary: 20
                }
            }
        })
        
        // person = readonly(person)
        person = shallowReadonly(person) // person里面的薪资可以改,其他都不能改
        
        // 返回一个对象(常用)
        return {
            sum ,
            ...toRefs(person)
        }
    }
}


5.3 toRaw 与 markRaw

toRaw: 作用: 将一个由reactive生成的响应式对象转为普通对象

使用场景: 用于读取响应式对象对应的普通对象,对这个普通对象的所有操作,不会引起页面更新

markRaw: 作用: 标记一个对象,使其永远不会再称为响应式对象。

应用场景:

1、有些值不应被设置为响应式的,例如复杂的第三方类库等。

2、当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能。

把普通数据变成响应式数据,我们用ref ,reactive

把响应式数据变成普通数据,我们用toRaw , markRaw

import {ref,reactive,toRefs,toRaw,markRaw} from 'vue'
export default {
    name: 'Demo',
    setup(){
        // 数据
        let sum = ref(0)
        let person = reactive({
            name: '糖糖糖',
            age: 22, 
            job: {
                j1: {
                    salary: 20
                }
            },
            //car:{} 第一种写法
        })
        // 方法
        function showRawPerson() {
            let p = toRaw(person)
            console.log(p)
            p.age ++ // 页面不会更新

            const sum = toRaw(sum)
            console.log(sum)
         }
        function addCar() {
            let car = {name: '奔驰',price: '40W'}
            // person.car = car // 给响应式对象里添加数据,也是响应式的
            person.car = markRaw(car) //使用markRaw car就不是响应式数据
            
        }
        // person = readonly(person)
        person = shallowReadonly(person) // person里面的薪资可以改,其他都不能改
        
        // 返回一个对象(常用)
        return {
            sum ,
            person,
           // ...toRefs(person),
            showRawPerson,
            addCar
        }
    }
}


5.4 customRef

作用 : 创建一个自定义的ref ,并对其依赖项跟踪和更新触发进行显示控制。

实现防抖效果:

<template>
  <input type="text" v-model="keyword"> 
  <h3>{{keyword}}</h3>
 </template>
 <script>
   import {ref, customRef} from 'vue'
   export default {
     name: 'Demo',
     setup() {
       // let keyword = ref('hello') //使用Vue准备好/提供的内置ref
       // 使用自定义一个ref叫myRef
       // 所谓的自定义ref就是一个函数 ,也不是我们完全自己写,我们要借助一个api 叫customRef
       function myRef(value,delay) { 
         let timer
         // 通过customRef去实现自定义
         return customRef((track,trigger) => {
         // vue官网提供这样写 ,既然是自定义那一定要写一个函数。而且也必须返回return出去一个对象。 
            return  { // 语法要求 ,get ,set 也是内置的
              get() { // 读
                  //return 100 // 返回值100
                  console.log(`有人从myRef这个容器中读取数据了,我把${value}给他了`)
                  track()// 通知Vue追踪value的变化(提前和get商量一下,让他认为这个value是有用的。)
                  return value // 第三步: return出去。 
              },
              set(newValue) { // 改
                  console.log(`有人把myRef这个容器中的数据改了,我把${newValue}给他了`)
                  clearTimeout(timer)
                  timer = setTimeout(()=>{
                    value = newValue // 第一步 改数据
                      trigger() // 第二步: 通知vue去重新解析模板
                  },delay)
               }
            }
         }) // 一定要返回return出去,不然就没意义


       }
      let keyword = myRef('hello',500) 
      //使用Vue准备好/提供的内置ref,500 是延迟时间。 目的是让数据延迟一会再执行       
       return {
         keyword
       }
     }
   }

</script>

5.5 provide 与 inject

provide 提供,inject 注入

作用: 实现祖孙组件间通信 (祖孙组件也叫跨级组件也叫祖与后代组件)

套路: 父组件有一个provide选项来提供数据,子组件有一个inject选项来开始使用这些数据 具体写法:

1、祖组件中: 
import { provide, reactive , toRefs } from 'vue'
setup() {
  ...
  let car = reactive({name: '奔驰',price: '40万'})
  provide('car',car)  // 给自己的后代组件提供数据
  return {...toRefs(car)} 
}
2、孙组件中: 
  import { inject } from 'vue'
setup(props,context) {
  ...
  const car = inject('car') // 使用这些数据,还是一个响应式的数据呢
  return { car }

  ...
}

5.6 响应式数据的判断

isRef: 检查一个值是否为一个ref对象

isReactive: 检查一个对象是否由reactive创建的响应式代理

isReadonly: 检查一个对象是否由readonly创建的只读代理

isProxy: 检查一个对象是否由reactive 或者 readonly 方法创建的代理

6. Composition API 的优势

  1. Options API(配置式的API) 存在的问题 使用传统OptionsAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改。

  2. Composition API(组合式的API) 的优势 我们可以更加优雅的组织我们的代码,函数,让相关功能的代码更加有序的组织在一起。

7. 新的组件

7.1 Fragment

  • 在Vue2中: 组件必须有一个根标签

  • 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中

  • 好处: 减少标签层级, 减小内存占用

7.2 Teleport

什么是Teleport?—— Teleport 是一种能够将我们的组件html结构移动到指定位置的技术。

<teleport to="移动位置">
	<div v-if="isShow" class="mask">
		<div class="dialog">
			<h3>我是一个弹窗</h3>
			<button @click="isShow = false">关闭弹窗</button>
		</div>
	</div>
</teleport>

7.3 Suspense

Suspense 官方说目前处于实验阶段

等待异步组件时渲染一些额外内容,让应用有更好的用户体验

使用步骤:

异步引入组件

import {defineAsyncComponent} from 'vue' // 定义一个异步组件 静态引入
const Child = defineAsyncComponent(()=>import('./components/Child.vue')) 
   
// 异步引入或 动态引入,要求必须传入一个函数并且有返回值

// 使用Suspense包裹组件,并配置好default 与 fallback

<template>
    <div class="app">
        <h3>我是App组件</h3>
        <Suspense>
            <template v-slot:default>
                <Child/>
            </template>
            <template v-slot:fallback>
                <h3>加载中.....</h3>
            </template>
        </Suspense>
    </div>
</template>

8. 其他

8.1 全局API的转移

  • Vue 2.x 有许多全局 API 和配置。例如:注册全局组件、注册全局指令等。
/注册全局组件
Vue.component('MyButton', {
  data: () => ({
    count: 0
  }),
  template: '<button @click="count++">Clicked {{ count }} times.</button>'
})

//注册全局指令
Vue.directive('focus', {
  inserted: el => el.focus()
}
  • Vue3.0中对这些API做出了调整:

将全局的API,即:Vue.xxx调整到应用实例(app)上

2.x 全局 API(Vue)3.x 实例 API (app)
Vue.config.xxxxapp.config.xxxx
Vue.config.productionTip移除
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties

8.2 其他改变

(1)、 data选项应始终被声明为一个函数。

(2)、过度类名的更改:

Vue2.x写法
.v-enter,
.v-leave-to {
  opacity: 0;
}
.v-leave,
.v-enter-to {
  opacity: 1;
}
Vue3.x写法

.v-enter-from,
.v-leave-to {
  opacity: 0;
}

.v-leave-from,
.v-enter-to {
  opacity: 1;
}

(3)、 移除keyCode作为 v-on 的修饰符,同时也不再支持config.keyCodes(兼容性差所以移除了)

(4)、 移除v-on.native修饰符 (a) 父组件中绑定事件

<my-component
  v-on:close="handleComponentEvent"
  v-on:click="handleNativeClickEvent"
/>

绑定了close,click (b) 子组件中声明自定义事件

<script>
  export default {
    emits: ['close'] // 用emits 来说明close是自定义事件
  }
</script>

(5)、 移除过滤器(filter)

过滤器虽然这看起来很方便,但它需要一个自定义语法,打破大括号内表达式是 “只是 JavaScript” 的假设,这不仅有学习成本,而且有实现成本!建议用方法调用或计算属性去替换过滤器。 .....

还有其他的,具体看官方文档。