Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示)

2024-06-04 2824阅读

目录

前言

概述

Vue3组合式api  VS  Vue2选项式api

基础部分

setup

选项式api的风格

组合式api的风格

区别

 响应式数据

ref

reactive

shallowReactive 与 shallowRef 

 计算属性和监听

 computed 函数

 watch 函数

 watchEffect

生命周期

 响应式数据只读

toRaw 返回代理的源

markRaw 标记对象拒绝代理

provide 与 inject 跨组件传值

判断是否为响应式数据

toRef 和 toRefs 解构响应式数据

 新组件

Fragment

Teleport 

Suspense

组合式函数 

全局的api及指令的变动

结语


前言

vue3已经出了好长一段时间了,最近闲来无事简单学习了一下,新增的东西还是挺多的,写一篇文章来记录一下。

概述

Vue3组合式api  VS  Vue2选项式api

谈到 vue3,首先想到的就是组合式api,很大程度的解决了vue2选项式api的缺点,那有啥缺点?当文件中的业务代码非常多的时候,阅读修改代码的时候是非常痛苦的,data,method,watch还有计算属性之间来回跳转, 我已经准备拔刀了。

下面这些图被疯转,很形象的展现了vue2和vue3的区别,可以看到组合式api就是将单个功能的状态,方法,计算属性等等需要用到的东西都组合在一起抽离成一个hook,也就是对应图4的function,最终再统一引入组合到一起。这样做的好处就是单个功能的代码都在一起,方便调式修改。

Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第1张 Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第2张

 Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第3张Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第4张

基础部分

setup

setup是vue3的一个新的配置项,只在初始化的时候执行一次,所有的组合式函数都在此使用。setup可以在选项式api的风格中使用也可以通过组合式api的风格 。通过代码简单对比一下。vue3推荐使用组合式。

选项式api的风格

import { ref } from 'vue'
export default {
  setup() {
    const sum = ref(1)
    return {
      sum,
    }
  },
}


  
    

v3

{{ sum }}

+1

组合式api的风格

import { ref } from 'vue'
const sum = ref(1)


  
    

v3

{{ sum }}

+1

区别

  1. 中的导入和顶层变量/函数都能够在模板中直接使用, 选项式则需要导出
  2. 打包出来的体积更小
  3. 对ts更友好

官网介绍的比较详细,感兴趣可以查看组合式 API 常见问答 | Vue.js 

 响应式数据

vue2中 data 函数返回的对象就是响应式的数据,但是在增加删除对象属性时不是响应式的,当然vue2中也有对应的解决方法,this.$set(), this.$delete(), 其实这也能够理解,毕竟vue2的响应式式基于 Object.defineProperty 实现的,这个函数只提供了 get 和 set 以及一些描述符 descriptor,并没有提供 add 和 delete 方法。

vue3中的响应式包含了两种形态, ref(底层还是Object.defineProperty进行数据劫持, 处理简单数据类型),reactive(使用es6的Proxy进行数据劫持,处理复杂数据类型),完全修复了vue2响应式的痛点,vue3的响应式更加的友好。

ref

ref 接受一个值,返回一个响应式对象,一般用来处理简单数据类型的响应式,但如果传入的值是对象 ref 会求助 reactive,返回RefImpl的实例简称ref对象。 此时可能会有疑惑,既然ref是一个响应式的对象,为什么模板中能正常解析。这是因为在解析templete时遇到ref对象会自动取其value属性,但是如果要在方法中修改ref创建的响应式数据,你的写法应该是这样的 state.value = xxx

import { ref } from 'vue'
const sum = ref(1)
function add() {
  sum.value++
}


  
    

v3

{{ sum }}

+1

reactive

为对象做深层!!!!响应式代理, 也就是如果对象有多层依旧是响应式的,返回一个Proxy实例, 如果传入一个字符串或者数字,它将不是响应式的。Proxy 对象用于创建一个对象的代理,从而实现基本操作的拦截和自定义(如属性查找、赋值、枚举、函数调用等)Proxy - JavaScript | MDN。Vue使用 Proxy 进行数据劫持, Reflect 进行反射修改 Reflect - JavaScript | MDN

Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第5张

import { reactive } from 'vue'
const person = reactive({
  name: '张三',
  age: 12,
  job: {
    j1: {
      jname: '前端开发',
    },
  },
})
function add() {
  person.hobby = ['唱', '跳', 'rap']
}
function deleteHB() {
  delete person.hobby
}


  
    

v3

{{ sum }}

姓名:{{ person.name }}

年龄:{{ person.age }}

工作:{{ person.job.j1.jname }}

爱好: {{ person.hobby }}

修改姓名 修改年龄 修改工作 增加爱好 删除爱好

shallowReactive 与 shallowRef 

shallowRef 直译过来意思是浅层的 ref,shallowRef 传入对象不会求助 reactive,仅仅对ref对象的 value 属性具有响应式。

shallowReactive 只处理对象第一层的响应式,  如果修改了深层的数据页面是不会响应的,但是会在下次页面更新中渲染出来。

Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第6张

import { shallowReactive, shallowRef, ref, reactive } from 'vue'
const shallowRef_jack = shallowRef({ name: 'jack', sex: '女' })
const shallowReactive_ben = shallowReactive({
  name: 'ben',
  sex: '女',
  child: {
    son: {
      name: '张三',
    },
  },
})
const ref_jack = ref({ name: 'jack', sex: '女' })
const reactive_ben = reactive({
  name: 'ben',
  sex: '女',
  child: {
    son: {
      name: '张三',
    },
  },
})


  
    

v3

shallowRef_jack: {{ shallowRef_jack }} 修改整个对象 修改对象属性

ref_jack: {{ ref_jack }} 修改整个对象 修改对象属性

shallowReactive_ben: {{ shallowReactive_ben }} 修改对象的第三层属性 修改对象第一层属性

reactive_ben: {{ reactive_ben }} 修改对象的第三层属性 修改对象第一层属性

h3 { font-size: 26px; border: 1px solid #ccc; padding: 20px; margin: 20px; } button { float: right; padding: 10px; font-size: 20px; }

 计算属性和监听

 computed 函数

计算属性有两种写法,作用和vue2一样,通过监听某个值的变化计算出一个新值

  • 只读的写法 :computed(() => xxxxxx),
  • 可读可写的写法:  computed({ get: () => xxxx, set: (val) => { xxxx } })
    import { ref, computed } from 'vue'
    const count = ref(1)
    const num1 = computed(() => count.value + 1)
    const num2 = computed({
      get() {
        return count.value + 1
      },
      set(val) {
        count.value = val + 1
      },
    })
    
    
      
        

    v3

    ref 定义的 count: {{ count }} count++

    计算属性 num1: {{ num1 }} num1++

    计算属性 num2: {{ num2 }} num2++

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第7张

     watch 函数

    watch 函数用来监听数据的变化,和vue2大体上都是相同的。

    参数列表:

    1. 参数1为需要监听的响应式对象(可以是单个对象,也可以是一个数组,也可以是一个getter函数),
    2. 参数2为监听对象发生变化时所执行的回调
    3. 参数3是一些配置项:immediate是否开启立即监听,deep是否开启深度监听,flush回调的触发时机,onTrack / onTrigger用于调试的两个函数

     Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第8张

    注意点: 

    1. 直接监听 reactive 定义的响应式对象默认开启了深度监听
    2. 通过 getter 形式监听响应式对象默认是浅层监听
    import { reactive, ref, watch } from 'vue'
    const count = ref(1)
    const person = reactive({
      name: 'ben',
      child: {
        son: {
          name: 'zs',
        },
      },
    })
    // 监听 ref 对象
    watch(count, (val, preVal) => {
      console.log('count变化了', val, preVal)
    })
    // 监听 reactive  定义的响应式对象
    watch(person, (val, preVal) => {
      console.log('person变化了', val, preVal)
    })
    watch([count, person], (val, preVal) => {
      console.log('person变化了或count变化了', val, preVal)
    })
    
    
      
        

    v3

    ref 定义的 count: {{ count }} count++

    reactive 定义的 person: {{ person }} 修改姓名 修改儿子姓名

     watchEffect

    watchEffect 函数用于监听传入的函数内访问的所有响应式数据的变化。白话一点就是回调里我用了谁我就监听谁,监听ref定义的响应式数据时,不要忘记 .value ,哥们就是这么智能。

    watch 和 watchEffect 都是监听数据变化的函数,和 react 中的 useState 放入依赖项有着异曲同工之妙。

    例子:切换下拉框中的 name ,模拟请求后台接口 

    import { onMounted, reactive, ref, watchEffect } from 'vue'
    const name = ref('jack')
    const info = [
      {
        id: 1,
        name: 'jack',
        child: {
          son: {
            name: 'zs',
          },
        },
      },
      {
        id: 2,
        name: 'ben',
        child: {
          son: {
            name: 'zs',
          },
        },
      },
    ]
    let data = ref([])
    async function getInfoByName(name) {
      const res = await new Promise((reslove) => {
        setTimeout(() => {
          reslove(info.filter((item) => item.name === name))
        }, 500)
      })
      data.value = res
    }
    watchEffect(async () => {
      getInfoByName(name.value)
    })
    
    
      
        

    v3

    {{ item.name }}的个人信息 {{ item }}

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第9张

    生命周期

    vue3的生命周期稍有变动,增加了 setup 钩子,且销毁前和销毁后的钩子命名更改为 beforeUnmount 和 unmounted,以下代码是验证的一些示例

    App.vue 

    import Demo from './Demo.vue'
    import Demo2 from './Demo2.vue'
    import { ref } from 'vue'
    const isComDestory = ref(true)
    const isOptionDestory = ref(true)
    
    
      
        

    v3 引入组合式子组件 销毁组合式子组件 引入选项式子组件 销毁选项式子组件

    button { padding: 20px; font-size: 16px; }

    Demo.vue 

    import {
      onMounted,
      onBeforeMount,
      onBeforeUpdate,
      onUpdated,
      onBeforeUnmount,
      onUnmounted,
      ref,
    } from 'vue'
    const sum = ref(1)
    console.log('子组件1 setup')
    onBeforeMount(() => {
      console.log('子组件1 onBeforeMount')
    })
    onMounted(() => {
      console.log('子组件1 onMounted')
    })
    onBeforeUpdate(() => {
      console.log('子组件1 onBeforeUpdate')
    })
    onUpdated(() => {
      console.log('子组件1 onUpdated')
    })
    onBeforeUnmount(() => {
      console.log('子组件1 onBeforeUnmount')
    })
    onUnmounted(() => {
      console.log('子组件1 onUnmounted')
    })
    
    
      
        

    我是子组件1

    {{ sum }} +1

    div { border: 1px solid #ccc; }

    Demo2.vue

    import { ref } from 'vue'
    export default {
      setup() {
        const sum = ref(1)
        console.log('子组件2 setup')
        return { sum }
      },
      beforeCreate() {
        console.log('子组件2 beforeCreate')
      },
      created() {
        console.log('子组件2 created')
      },
      beforeMount() {
        console.log('子组件2 beforeMount')
      },
      mounted() {
        console.log('子组件2 mounted')
      },
      beforeUpdate() {
        console.log('子组件2 beforeUpdate')
      },
      updated() {
        console.log('子组件2 updated')
      },
      beforeUnmount() {
        console.log('子组件2 beforeUnmount')
      },
      unmounted() {
        console.log('子组件2 unmounted')
      },
    }
    
    
      
        

    我是子组件2

    {{ sum }} +1

    div { border: 1px solid #ccc; }

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第10张

    由于录频录不了控制台,打印结果看下图

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第11张

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第12张 Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第13张

     响应式数据只读

    vue3提供了两个api,限制响应式数据为只读,不可修改。分别为 readonly(深层只读) 和shallowReadonly (浅层只读)

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第14张

    import { ref, reactive, readonly, shallowReadonly } from 'vue'
    const sum = readonly(ref(1))
    const p1 = readonly(
      reactive({
        name: 'ben',
        child: {
          son: {
            name: 'jack',
          },
        },
      })
    )
    const p2 = shallowReadonly(
      reactive({
        name: 'ben',
        child: {
          son: {
            name: 'jack',
          },
        },
      })
    )
    function edit() {
      sum.value = 2
      p1.name += '!'
      p1.child.son.name += '&'
    }
    function editShallow() {
      p2.name += '!'
      p2.child.son.name += '&'
    }
    
    
      
        

    v3

    readonly: {{ sum }}

    readonly: {{ p1 }}

    shallowReadonly: {{ p2 }}

    修改深层只读数据 修改浅层只读数据

    toRaw 返回代理的源

    toRaw的功能官网的解释很清晰, 可以返回由 reactive()、readonly()、shallowReactive() 或者 shallowReadonly() 创建的代理对应的原始对象

    import {
      ref,
      reactive,
      readonly,
      shallowReadonly,
      shallowReactive,
      toRaw,
    } from 'vue'
    const p1 = readonly(
      reactive({
        name: 'a',
        child: {
          son: {
            name: 'as',
          },
        },
      })
    )
    const p2 = shallowReadonly(
      reactive({
        name: 'b',
        child: {
          son: {
            name: 'bs',
          },
        },
      })
    )
    const p3 = reactive({
      name: 'c',
      child: {
        son: {
          name: 'cs',
        },
      },
    })
    const p4 = shallowReactive({
      name: 'd',
      child: {
        son: {
          name: 'ds',
        },
      },
    })
    console.log('toRaw p1 readonly', toRaw(p1))
    console.log('toRaw p2 shallowReadonly', toRaw(p2))
    console.log('toRaw p3 reactive', toRaw(p3))
    console.log('toRaw p4 shallowReactive', toRaw(p4))
    
    
      
    
    
    

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第15张

    markRaw 标记对象拒绝代理

    markRaw()将对象标记为不可代理,返回其本身。本身上多了一个 __v_skip 属性表示忽略代理。强行代理代理是无效的,返回的还是其本身而不是响应式对象。

    import { markRaw, reactive } from 'vue'
    const p1 = {
      name: 'a',
      child: {
        son: {
          name: 'as',
        },
      },
    }
    const noProxy_p1 = markRaw(p1)
    console.log('不可代理对象', noProxy_p1)
    console.log('reactive 代理不可代理对象', reactive(noProxy_p1))
    
    
      
    
    
    

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第16张

     provide 与 inject 跨组件传值

    使用 provide 与 inject 进行跨组件传值十分方便。以父子孙为例,父组件 provide ('name',value) 子组件 inject ('name') 即可

    Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第17张

     父组件

    import { reactive, provide } from 'vue'
    import Demo from './Demo.vue'
    const obj = {
      name: 'a',
      child: {
        son: {
          name: 'as',
        },
      },
    }
    const person = reactive(obj)
    provide('person', person)
    
    
      
        

    父组件

    {{ person }}

    .father { padding: 10px; background: orange; }

     子组件

    import Demo2 from './Demo2.vue'
    
    
      
        

    子组件

    div { padding: 10px; background: salmon; border: 1px solid #ccc; }

    孙组件

    import { ref, inject } from 'vue'
    export default {
      setup() {
        const person = inject('person')
        return { person }
      },
    }
    
    
      
        

    孙组件

    {{ person }}

    .sonson { background: sandybrown; border: 1px solid #ccc; }

    判断是否为响应式数据

    • isRef(data)判断data是否是通过ref创建的响应式数据
    • isReactive(data)判断data是否是通过reactive创建的响应式数据
    • isReadonly(data)判断data是否是通过readOnly创建的只读数据
    • isProxy(data)判断data是否为Proxy代理对象

      Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第18张

      import {
        reactive,
        readonly,
        ref,
        isProxy,
        isReactive,
        isRef,
        isReadonly,
      } from 'vue'
      const person = reactive({
        name: 'a',
        child: {
          son: {
            name: 'as',
          },
        },
      })
      const num = ref(1)
      const str = readonly(ref('str'))
      console.log(isRef(num))
      console.log(isReactive(person))
      console.log(isReadonly(str))
      console.log(isProxy(person), isProxy(str))
      
      
      
      

      toRef 和 toRefs 解构响应式数据

      当响应式对象的属性过多且页面用到很多次的时候, toRef 和 toRefs 可以进行响应式解构,解构出来的数据依旧具备响应式的能力。下面的例子是在  中进行演示的,setup()中的需要显示的返回

       toRef

      import { reactive, toRef } from 'vue'
      const person = reactive({
        name: 'a',
        age: 18,
        child: {
          son: {
            name: 'as',
          },
        },
      })
      const personName = toRef(person, 'name')
      const personAge = toRef(person, 'age')
      const personSonName = toRef(person.child.son, 'name')
      
      
        
          

      toRef 解构出 person的name ----- {{ personName }}

      toRef 解构出 person的age ----- {{ personAge }}

      toRef 解构出 person的child的son的name ----- {{ personSonName }}

      toRef 解构出 person的name ----- {{ personName }}

      toRef 解构出 person的age ----- {{ personAge }}

      toRef 解构出 person的child的son的name ----- {{ personSonName }}

      toRef 解构出 person的name ----- {{ personName }}

      toRef 解构出 person的age ----- {{ personAge }}

      toRef 解构出 person的child的son的name ----- {{ personSonName }}

      修改person的name 修改person的age person的child的son的name

       Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第19张

       toRefs

      import { reactive, toRefs } from 'vue'
      const person = reactive({
        name: 'a',
        age: 18,
        child: {
          son: {
            name: 'as',
          },
        },
      })
      const { name, age, child } = toRefs(person)
      
      
        
          

      toRefs 解构出 person的name ----- {{ name }}

      toRefs 解构出 person的age ----- {{ age }}

      toRefs 解构出 person的child的son的name ----- {{ child.son.name }}

      toRefs 解构出 person的name ----- {{ name }}

      toRefs 解构出 person的age ----- {{ age }}

      toRefs 解构出 person的child的son的name ----- {{ child.son.name }}

      toRefs 解构出 person的name ----- {{ name }}

      toRefs 解构出 person的age ----- {{ age }}

      toRefs 解构出 person的child的son的name ----- {{ child.son.name }}

      修改person的name 修改person的age person的child的son的name

      Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第20张

       新组件

      Fragment

      在vue2中模板标签内必须包裹一层根标签,vue3中则不需要。vue3会为多个跟标签包裹一层Fragment。这是写法上的优化。前面很多例子的代码中我都包裹了一层根标签,这是由于我的编辑器的eslint的问题,去掉根标签也可以正常运行。

      有根标签的编译结果

      Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第21张 没有根标签的编译结果

      Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第22张

      Teleport 

      Teleport 组件的功能是将元素渲染到任意的页面位置中,直接扣过来官网的例子。

      下列代码主要表达的是:点击按钮将弹框插入到 body 标签下 

       ModalButton.vue

        
          Open full screen modal! (With teleport!)
        
        
          
            
              I'm a teleported modal! (My parent is "body")
              Close
            
          
        
      
      
      import { ref } from 'vue'
      export default {
        name: 'modal-button',
        setup() {
          const modalOpen = ref(false)
          return {
            modalOpen,
          }
        },
      }
      
      
      .modal {
        position: absolute;
        top: 0;
        right: 0;
        bottom: 0;
        left: 0;
        background-color: rgba(0, 0, 0, 0.5);
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
      }
      .modal div {
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        background-color: white;
        width: 300px;
        height: 300px;
        padding: 5px;
      }
      
      

       App.vue

        

      App

      import ModalButton from './ModalButton.vue' export default { setup() { return {} }, components: { ModalButton, }, }

       Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第23张

      Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第24张

      Suspense

      Suspense 组件用于将异步组件包裹,提供一个过渡UI在异步完成之前。

      Suspense 组件提供两个插槽:

      1. #default   默认插槽 存放异步组件
      2. #fallback  备用插槽 存放过渡UI

      异步组件:

      1. 带有异步 setup() 钩子的组件。这也包含了使用  时有顶层 await 表达式的组件。

      2. defineAsyncComponent

       App.vue

        
          

      App

      加载中.... import Demo from './Demo.vue'

       Demo.vue

      const res = await new Promise((resolve) => {
        setTimeout(() => {
          resolve({ name: 'zs', age: 12, sex: '男' })
        }, 1000)
      })
      
      
        
          

      异步组件

      {{ res }}

      div { padding: 10px; background: salmon; border: 1px solid #ccc; }

      Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第25张

      组合式函数 

      组合式api的优点之一式将单个功能代码组合在一起,如果是可以复用的逻辑,那么可以抽离为一个组合式函数或者称为自定义hook,在需要该逻辑的地方导入即可

      例子:提供一个组合函数,此函数在当前组件中监听鼠标移动事件,并将坐标显示出来,组件卸载前清掉事件。

      App.vue

        
          

      App

      销毁子组件1 销毁子组件2

      import { ref } from 'vue' import Demo from './Demo.vue' import Demo2 from './Demo2.vue' const Demo1Visible = ref(true) const Demo2Visible = ref(true)

      Demo1.vue

        
          

      子组件1

      x坐标为 {{ x }}, y坐标为{{ y }}

      import useMouse from './mouse' const { x, y } = useMouse('.demo_1') .demo_1 { height: 100px; background: salmon; }

      Demo2.vue 

        
          

      子组件2

      x坐标为 {{ x }}, y坐标为{{ y }}

      import useMouse from './mouse' const { x, y } = useMouse('.demo_2') .demo_2 { height: 100px; background: salmon; }

       Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第26张

      Vue3基础看这一篇就够了(万字长篇,附实例代码及效果演示) 第27张

      全局的api及指令的变动

      API 参考 | Vue.js,大家先自行参考,后续深入学习时再进行更新。

      结语

      • 如果想在 vue3 中使用 element,请下载 element-plus
      • vue3 的文档是最全的。Vue.js - 渐进式 JavaScript 框架 | Vue.js

    免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们,邮箱:ciyunidc@ciyunshuju.com。本站只作为美观性配图使用,无任何非法侵犯第三方意图,一切解释权归图片著作权方,本站不承担任何责任。如有恶意碰瓷者,必当奉陪到底严惩不贷!

    目录[+]