vue2 到 vue3学习(1)

337 阅读7分钟

1. vue2与vue3 的区别

  1. 性能提升
  2. Proxy 代替 defineProerty 实现响应式
  3. 重写虚拟DOM算法和 Tree-Shaking(术语表)
  4. 更好的支持TypeScript
  5. 新特性
    • Composition API(组合式API)
    • 新的内置组件
    • 其他改变

2. 创建Vue3工程

2.1 使用vue-cli 创建

  • vue-cli 版本必须在 4.5.0 以上
// vue-cli 创建初始化工程
// 需要装 vue-cli 手脚架
vue create vue_demo

2.2 使用vite 创建

  • vite 是新一代构建工具(老的是 webpack
    • 优点
      • 开发环境中,无需打包,可快速冷启动
      • 更好的热重载(HMR)代码更新,页面刷新(webpack 也有)
      • 按需编译,不再等待整个应用变异
// vite 创建初始化工程
保证 node 12.0 以上 及 npm/yarn 版本即可
npm init vite@latest my-vue-app (没有装依赖) 

3.vue2 与 vue3 变化分析

3.1 .vue文件中的template 可以没有 根标签

3.2 main.js 变化

//* vue3 main.js
// 引入不再是Vue构造函数了(要用到new) ,而是 createApp工厂函数
import  { createApp } from 'vue'
import App from './App.vue'
// 1. 创建应用实例对象,(类似 vue2 中的 vm, 但是app 体量比vm 更加轻遍)
const app = createApp(App)
// 2.挂载
app.mount('#app')



//* vue2 main.js
// 1. 创建 vm 实例
const vm = new Vue({
    render:h => h(App)
})
// 2. 挂载
vm.$mount('#app')

3.3 安装支持vue3 的 新的chrome 插件

无VPN:极简插件 vue2插件 与vue3 同时开启,都会亮,但实际上vue3 插件起作用

4. Composition API(组合式API)详解

4.1 setup 函数

  1. 新的配置项,值是一个函数
  2. 函数内部写数据,方法等数据,可以在模版中直接使用
    • 我的理解是 将vue2 中的 datamethodswatch、钩子函数 以函数的形式 写在setup函数内部
  • 注意事项
    • 函数返回值
      • 如果返回了一个对象,模版可以直接使用
      • 如果返回 渲染函数,则会替换当前模版的内容,使用 渲染函数的内容
    • 可以写vue2 配置,不建议(不深入了)
    • setup 不能是一个 async 函数
    • setup 函数在 beforeCreate函数执行前 ,且其中的this的值为undefined
    • setup 参数
      • props : 组件外部传递过来,组件内部接收的属性(proxy对象)
      • context: 上下文对象
        • attr: 相当于vue2 中的 this.$attr,用于 接收父组件传递过来的变量,但在子组件中没有接收到
        • slots: 相当于 vue2 中的 this.$slots
          • vue3 在父组件 中使用时,最好使用 v-slot:aaa的写法
        • emit: 相当于 vue2 中的 this.$emit
          • 子组件使用时,要在 emits 对象进行配置,否则有警告
import { setup ,ref } from 'vue'
... 

props:{},    // 与vue2 相同  ,父组件传递了 就要接收,没有接收会有警告
emits:['事件名1','事件名2'], // 方法中用到了 就要在此声明,没有就会警告
setup(props,context){
    // 数据  通过 ref 将数据变为响应式
    let name = ref('zs') ;
    let age = ref(18);
    // 事件
    function change(){
        context.emit('事件名1')
    }
    return {
        name,
        age,
    }
    
}

4.2 ref 函数(reference)

  1. 定义一个响应类型的数据,可以定义基本类型 和 复杂数据类型(与 reactive 不同)

    1. 使数据变成响应式的数据,将数据变成引用对象
  2. 只有 setup 函数,当其中的变量并不是响应式

  3. ref将数据 包装了一层

    • 简单数据类型:还是使用 defineProerty 数据劫持
      • 包装成了 RefImpl 对象实例,修改的时候的时候需要 str.value = 1
    • 复杂数据类型: 使用了 reactive 函数 见 4.3
      • 包装成了 RefImpl 对象实例 进一步 使用了 reactive 函数 包装成了 Proxy 对象实例,修改的时候需要 obj.value.str = '1'
  4. 简单数据类型 使用 ref()

import { setup ,ref } from 'vue'
... 
setup(){
    // 数据  通过 ref 将数据变为响应式
    let name = ref('zs') ;
    let age = ref(18);
    // 不建议包裹 复杂数据类型。推荐用 reactive 
    let obj = ref({
        a:1,
        chilren:{
            b:2
        }
    })
    // 方法
    
    function change(){
        // 修改数据 
        name.value = 'ls'
        
        obj.value.a = 2
        obj.value.children.b = 3
    }
    
    return {
        name,
        age,
        change
    }
}

4.3 reactive 函数

  1. 定义一个对象类型的响应式数据,只能使用复杂数据类型,不能使用简单数据类型
  2. const 代理对象 = reactive(源对象)
    • 代理对象:Proxy实例对象,简称proxy 对象 4 内部基于ES6的 Proxy 实现,通过 代理对象 操作 源对象 内部数据 见 4.4
import { setup ,ref } from 'vue'
... 
setup(){
    // 数据  通过 ref 将数据变为响应式
    let name = ref('zs') ;
    let age = ref(18);
    // 不建议包裹 复杂数据类型。推荐用 reactive 
    let obj = ref({
        a:1,
        chilren:{
            b:2
        }
    })
    // 方法
    
    function change(){
        // 修改数据 
        name.value = 'ls'
        
        obj.value.a = 2
        obj.value.children.b = 3
    }
    
    return {
        name,
        age,
        change
    }
}

4.4 vue2与vue3响应式原理

4.4.1 vue2 响应式原理

  • 实现原理
    • 对象类型: 通过 Object.defineProperty()对属性的读取,修改进行拦截(数据劫持)
    • 数组类型: vue 通过包裹 Array 构造函数上的 push、shift等7个方法进行包装
  • 存在问题
    • 对象新增属性、删除属性,界面不会更新
    • 通过下标修改数组,界面不会更新
        // 源数据
        const person = {
            name: 'zs',
            age: 18
        }

        let p = {}
        // 数据劫持: 劫持 源对象 身上的所有属性
        for (let k in person) {
            Object.defineProperty(p, k, {
                // 访问对象属性时触发
                get() {
                    return person[k]
                },
                // 修改对象身上的 属性时触发
                set(value) {
                    console.log('数据发生了修改,更新视图');
                    person[k] = value
                }
            })
        }

4.4.2 vue3 响应式原理

  • 实现原理
    • 通过 Proxy(代理):拦截对象中任意属性的变化 (监听到数据变化)
    • 通过 Reflect(反射):对 被代理的对象(源对象) 进行操作 (对源数据进行修改)
    • 总结来说,就是 通过 proxy 代理源对象,通过操作 代理对象,监听到代理对象的变化,响应视图,并使用 反射(Reflect) 进行修改源数据
        // vue3 响应式 简易实现
        //#region 
        // 源数据
        const person = {
            name: 'zs',
            age: 18
        }

        // 代理对象          Reflect 反射对象
        let p = new Proxy(person, {
            // 新增或修改 对象属性时触发  target 为源数据 就是person  propName就时访问对象
            // 访问 对象属性时触发
            get(target, propName) {
                // return target[propName]
                return Reflect.get(target, propName)
            },
            set(target, propName, value) {
                console.log('数据变化,更新视图');
                // 新增/修改源数据
                // target[propName] = value
                Reflect.set(target, propName, value)
            },
            // 删除 对象属性时触发
            deleteProperty(target, propName) {
                console.log('数据变化,更新视图');
                // 删除 源数据
                // delete target[propName]
                return Reflect.defineProperty(target, propName)
            }
        })

4.5 computed 计算属性

  • 与vue2 基本一样,不过是 组合式API 写法
import { setup ,reactive ,computed } from 'vue'
... 
setup(){
     // 响应式数据 
     let person = reactive({
         name:'zs',
         age:18
     })
     
     // 计算属性 - 简写
     person.full = computed(()=>{
         return person.name +'-' +person.age
     )
     
     // 计算属性 - 完整写法
     person.full = computed({
         set(value){
             const arr = value.spilt('-')
             person.name = arr[0]
         },
         get(){
            return person.name +'-' +person.age
         }
     })
    
    return {
        person
    }
}

4.6 watch 监听属性

  • refreactive 监听语法有 5种基本语法+1种特殊语法。 reactive deep坑点,代码如下
import { setup ,ref,reactive ,watch } from 'vue'
... 
setup(){
     // 响应式数据 
     let person = reactive({
         name:'zs',
         age:18,
         job:{
             j1:{
                 salary:20
             }   
         }
     })
     let sum = ref(0)
     let msg = ref('hello')
     
     
     // 情况一: 监听到ref 定义的一个响应式数据
     watch(sum,(newValue,oldValue)=>{
         console.log(newValue,oldValue); // 0 undefined
     },{immediate:true})
     
     // 情况二: 监听到ref 定义的多个响应式数据
     watch([sum,msg],(newValue,oldValue)=>{
         console.log(newValue,oldValue); // [0,'hello'] []
     },{immediate:true})
     
     // 情况三: 监听到reactive 定义的一个响应式数据的全部属性,
     /* 
         注意:此时无法获取 oldValue
         注意:强制开始起 deep:true 无法关闭
     */     
     watch(person,(newValue,oldValue)=>{
         console.log(newValue,oldValue); // newValue 和 oldValue 是一个东西
     },{immediate:true}) 
     
     // 情况四: 监听到reactive 定义的一个响应式数据的一个 简单属性,
     /* 
         注意:此时无法获取 oldValue
         注意:强制开始起 deep:true 无法关闭
     */     
     watch(()=> person.name ,(newValue,oldValue)=>{
         console.log(newValue,oldValue); // 
     },{immediate:true}) 
     
     // 情况五: 监听到reactive 定义的一个响应式数据的多个 简单属性,
     /* 
         注意:此时无法获取 oldValue
         注意:强制开始起 deep:true 无法关闭
     */     
     watch([()=> person.name,()=> person.age] ,(newValue,oldValue)=>{
         console.log(newValue,oldValue); // 
     },{immediate:true}) 
     
     // 特殊情况: 监听到reactive 定义的一个响应式数据的一个 复杂数据类型 属性,
     /* 
         注意:此时无法获取 oldValue
         注意:deep 没开启 就无法检测到
     */     
     watch(()=> person.job ,(newValue,oldValue)=>{
         console.log(newValue,oldValue); // 
     },{immediate:true,deep:true}) 
     
   
    
    return {
        person,
        sum,
        msg
    }
}

4.7 watchEffect 监听

  • 智能监听:不指定监视那个数据,回调中用到了哪个变量,就监听了哪些,默认开启了 immediate
import { setup ,ref,reactive ,watchEffect } from 'vue'
... 
setup(){
     // 响应式数据 
     let person = reactive({
         name:'zs',
         age:18,
         job:{
             j1:{
                 salary:20
             }   
         }
     })
     let sum = ref(0)
     let msg = ref('hello')
     
     
     // watchEffect 不指定监视那个数据,回调中用到了哪个变量,就监听了哪些,默认开启了 immediate         
     watchEffect(()=>{
         const x1 = sum.value
         const x2 = 
         console.log('data change'); // 0 undefined
     })

    
    return {
        person,
        sum,
        msg
    }
}

4.8 生命周期钩子函数

  • vue3 变更的钩子,其他的都一样
    • beforeDestroy => beforeUnmounted
    • destroy => unmounted
  • 支持 vue2 写法 (与 setup 平级)
  • 支持 组合式API 写法 (在setup内部)
    • 名称变更
      • beforeCreate => setup
      • created => setup
      • beforeMount => onBeforeMount
      • mounted => onMounted
      • beforeUpdate => onBeforeUpdate
      • updated => onUpdated
      • beforeUnmount => onBeforeUnmount
      • unmounted => onUnmounted
    • 组合式API 没有 beforeCreatecreated 了 ,由 setup 代替了
  • 支持 vue2 写法 和 组合API 写法 ,两种钩子函数有顺序(不建议混用)

4.9 自定义 hook 函数

  • hook: 本质是一个函数,把 setup 中的 Composition API 进行了封装
  • 类似 vue2 中的 mixin
  • 自定义 hook 可以使 setup 中的逻辑更加清晰