Vue3学习笔记

Vue3学习笔记

之前一直没有很系统性的去看过vue3的相关知识,刚好在入职后一个多月的时间,遇到了第一个春节假期,因此在这个期间看了vue3的相关知识。不然项目做起来太难受…

Vue3创建项目的两种方式
  • 脚手架

  • 通过vite安装

什么是vite? —新一代的前端构建工具(类似于webpack)

优势:无需打包,快速启动

初始化工程

attention: Vue3组建中的模板结构可以没有根标签

常用组合式api

setup函数

Vue3中一个新的配置项,值为一个函数

组件中用到的数据、方法等,均要配置在setup中

最后要将setup中的变量和函数写在return中

<template>
  <h1>我是App组件</h1>
  <h4>姓名:{{name}}</h4>
  <h4>年龄:{{age}}</h4>
  <button @click="sayHi">sayHi</button>
</template>

<script>

export default {
  name: 'App',
  setup(){
    let name = 'joy';
    let age = 23;
    function sayHi() {
      console.log('Hi')
    };
    return{
    name,
    age,
    sayHi
  }
  },
}
</script>

attention:

  • Vue2配置(data,methods,computed)中可以访问到setup中的属性、方法

  • 但在setup中不能访问Vue2配置(data、methods,computed)

  • 如果有重名,setup优先

async不能用来修饰setup,因为返回值是一个promise对象,不能通过return方式返回并访问

ref函数
ref处理简单类型

ref函数,使变量转换成响应式

<template>
  <h1>信息登记</h1>
  <h4>姓名:{{name}}</h4>
  <h4>年龄:{{age}}</h4>
  <button @click="changeInfo">一键修改</button>
</template>

<script>
import {ref} from 'vue'
export default {
  name: 'App',
  setup(){
    let name = ref('joy');
    let age = ref(23);

    function changeInfo() {
      name.value = 'zoey'
      age.value = 18
      // console.log(name,age)
    }

    return{
    name,
    age,
    changeInfo
  }
  },
}
</script>
Ref处理复杂类型

基本类型的数据:响应式依然是靠object.defineProperty()的get和set完成的

对象类型的数据:内部借助了Vue3中的一个新函数 ----- reactive函数

<template>
  <h1>信息登记</h1>
  <h4>姓名:{{name}}</h4>
  <h4>年龄:{{age}}</h4>
  <h4>职业:{{job.type}}</h4>
  <h4>薪水:{{job.salary}}</h4>
  <button @click="changeInfo">一键修改</button>
</template>

<script>
import {ref} from 'vue'
export default {
  name: 'App',
  setup(){
    let name = ref('joy');
    let age = ref(23);
    let job = ref({
      type:'前端开发工程师',
      salary:'30K'
    })

    function changeInfo() {
      job.value.type = '算法工程师'
      job.value.salary = '50K'
    }

    return{
    name,
    age,
      job,
    changeInfo
  }
  },
}
</script>
reactive函数

处理对象类型的响应数据,基本类型不要用reactive(无法处理)

reactive定义的响应式数据是“深层次的”

内部基于ES6的Proxy实现,通过代理对象操作源对象内部数据进行操作

<template>
  <h1>信息登记</h1>
  <h4>姓名:{{person.name}}</h4>
  <h4>年龄:{{person.age}}</h4>
  <h4>职业:{{person.job}}</h4>
  <h4>薪水:{{person.salary}}</h4>
  <button @click="changeInfo">一键修改</button>
</template>

<script>
import {reactive} from 'vue'
export default {
  name: 'App',
  setup(){
     const person = reactive({
       name:'joy',
       age : 23,
       job :{
         title:'前端开发工程师',
         salary:'22.5K'
       }
     })

    function changeInfo() {
       person.title = '算法工程师',
           person.salary = '50K'
    }

    return{
    person,
    changeInfo
  }
  },
}
</script>
Vue3中的响应式原理
Vue2的响应式原理

实现原理:

  • 对象类型:通过Object.defineProperty() 对属性的读取、修改进行拦截(数据劫持)

  • 数组类型:通过重写更新数组的一系列方法来实现拦截(对数组的变更方法进行了包裹)

Object.defineProperty(data,'count',{
    get(){},
    set(){}
})

存在问题:

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

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

Vue3的响应式原理
reactive对比ref
  • 从定义数据角度对比

    ref用来定义:基本类型数据

    reactive用来定义:对象(或数组)类型数据

    attention:ref也可以用来定义对象(或数组)类型数据,它内部会自动通过reactive转为代理对象

  • 从原理角度对比

ref通过Object.defineProperty() 的get 与 set来实现响应式(数据劫持)

reactive通过使用Proxy来实现响应式(数据劫持),并通过Reflect操作源对象内部的数据

  • 从使用角度对比

ref定义的数据:操作数据需要.value,读取数据时模板中直接读取不需要.value

reactive定义的数据:操作数据与读取数据:均不需要.value

setup的两个注意点
  • setup执行的时机

在beforeCreate之前执行一次,this是undefined

  • setup的参数

props:值为对象,包含:组件外部传递过来,且组件内部声明接收了的属性

context:上下文对象

attrs:值为对象,包含:组件外部传递过来,但没有在props配置中声明的属性

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

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

context

Demo组件

<template>
   <h1>一个人的信息</h1>
   <h4>姓名:{{person.name}}</h4>
   <h4>年龄:{{person.age}}</h4>
  <button @click="test">点击我进行测试</button>
</template>

<script>
import {reactive} from "vue";
export default {
  name: "Demo",
  props:{
    name:{
      type:String,
      required:true
    },
    age:{
      type:Number,
      required: true
    }
  },
// 注册事件
  emits:['hello'],
  setup(props,context){
    console.log('---context---',context)

    let person = reactive({
      name:'joy',
      age:23
    })

    // methods
    function test(){
    //  组件间传值
      context.emit('hello',66)
    }

    return{
      person,
      test
    }
  }
}
</script>

<style scoped>

</style>

App.vue

<template>
  <Demo name="zoey" age=18
  @hello="helloMsg"
  ></Demo>
</template>

<script>
import Demo from "./components/Demo.vue";
export default {
  name: 'App',
  components:{Demo},
  setup(){
    function helloMsg(value){
      alert(`你好啊,父组件接收到了Demo的传值${value}`)
    }

    return{
      helloMsg
    }
  }
}
</script>
slots

Demo组件

<template>
   <h1>一个人的信息</h1>
   <h4>姓名:{{person.name}}</h4>
   <h4>年龄:{{person.age}}</h4>
  <button @click="test">点击我进行测试</button>
   <hr>
  <slot name="god">我是默认值</slot>
</template>

<script>
import {reactive} from "vue";
export default {
  name: "Demo",
  props:{
    name:{
      type:String,
      required:true
    },
    age:{
      type:Number,
      required: true
    }
  },
  emits:['hello'],
  setup(props,context){
    // props 接收传参
    // console.log('---props---',props)

    // emit 触发自定义事件
    // console.log('---context---',context.emit)

    // slot插槽
    console.log('---context---',context.slots)

    let person = reactive({
      name:'joy',
      age:23
    })

    // methods
    function test(){
      context.emit('hello',66)
    }

    return{
      person,
      test
    }
  }
}
</script>

<style scoped>

</style>

App.vue

<template>
  <Demo name="zoey" age=18 @hello="helloMsg">
    <template v-slot:god>
      <span>大神之路</span>
    </template>
  </Demo>
</template>

<script>
import Demo from "./components/Demo.vue";
export default {
  name: 'App',
  components:{Demo},
  setup(){
    function helloMsg(value){
      alert(`你好啊,父组件接收到了Demo的传值${value}`)
    }

    return{
      helloMsg
    }
  }
}
</script>
Computed计算属性

集成变成了computed()函数

使用时必须先引用,里面必须写回调函数(普通函数 or 箭头函数)

计算属性简写
计算属性全写
<template>
   <h1>一个人的信息</h1>
   姓:<input type="text" v-model="person.lastName">
  <br>
   名:<input type="text" v-model="person.firstName">
  <br>
  姓名:<span>{{person.fullName}}</span>
</template>

<script>
import {reactive,computed} from "vue";
export default {
  name: "Demo",

  setup(){
    let person = reactive({
      firstName:'joy',
      lastName:'Zhang'
    })

    // 计算属性
    // 计算属性-简写
    // 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,
    }
  }
}
</script>

<style scoped>

</style>
watch属性
situationI:监视ref定义的响应式数据

Demo

<template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button>
</template>

<script>
import {ref,watch} from "vue";
export default {
  name: "Demo",
  setup(){
   let sum = ref(0)
    //watch
    watch(sum,(newValue,oldValue)=>{
      console.log('sum changed',newValue,oldValue)
    })
    return{
      sum
    }
  }
}
</script>

<style scoped>

</style>
situationII: watch监视多个ref响应式数据的改变

Demo

<template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button>
   <h2>当前信息为{{msg}}</h2>
  <button @click="msg+='!'">点击</button>
</template>

<script>
import {ref,watch} from "vue";
export default {
  name: "Demo",
  setup(){
   let sum = ref(0)
    let msg = ref('新年好')

    watch([sum,msg],(newValue,oldValue)=>{
      console.log('sum/msg changed',newValue,oldValue)
    },{immediate:true})

    return{
      sum,
      msg
    }
  }
}
</script>

<style scoped>

</style>
Situation III: watch监听reactive定义的数据
  • 使用watch监听reactive定义的数据无法获取oldValue

  • 在vue3中,不管对象的嵌套有多深,只要是用reactive定义的响应式数据,使用watch均可以开启深度监听

  • 默认开启深度监听,并且无法通过{deep:false}的方式关闭

<template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button>
  <hr>
   <h2>当前信息为{{msg}}</h2>
  <button @click="msg+='!'">点击</button>
  <hr>
  <h2>姓名:{{person.name}}</h2>
  <h2>年龄:{{person.age}}</h2>
  <h2>职业:{{person.job.job1.title}}</h2>
  <h2>薪水:{{person.job.job1.salary}}K</h2>
  <button @click="person.name+='~'">修改姓名</button>
  <button @click="person.age++">增长年龄</button>
  <button @click="person.job.job1.salary++">涨薪</button>
</template>

<script>
import {ref,watch,reactive} from "vue";
export default {
  name: "Demo",
  setup(){
   let sum = ref(0)
    let msg = ref('新年好')
    let person = reactive({
      name:'joy',
      age:23,
      job:{
        job1:{
          title:'前端开发工程师',
          salary:30
        }
      }
    })

    watch(person,(newValue,oldValue)=>{
      console.log('person changed',newValue,oldValue)
    },{deep:true})


    return{
      sum,
      msg,
      person
    }
  }
}
</script>

<style scoped>

</style>
Situation IV:watch监听reactive定义的一个响应式数据中的一个属性

监听对象的某个属性,必须写成箭头函数的返回值形式,() => {}

<script>
    watch(()=>person.job.job1.salary,(newValue,oldValue)=>{
      console.log('person changed',newValue,oldValue)
    })
  }
}
</script>
Situation V:watch监视reactive定义的一个响应式数据中的某些属性
<script>
import {ref,watch,reactive} from "vue";

    watch([()=>person.name,()=>person.age,()=>person.job.job1.salary],(newValue,oldValue)=>{
      console.log('person changed',newValue,oldValue)
    })

</script>
特殊情况

当监听的数据为reactive响应式数据中某个属性中的深层属性,需要添加deep属性

无法监听的情况

watch(()=>person.job,(newValue,oldValue)=>{
      console.log('job changed',newValue,oldValue)
    })

加上deep后可以被监听

watch(()=>person.job,(newValue,oldValue)=>{
      console.log('job changed',newValue,oldValue)
    },{deep:true})

注意:

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

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

watchEffect函数
  • watch:既要指明监视的属性,也要指明监视的回调

  • watchEffect:不用指明监视哪个属性,监视的回调中用到哪个属性,那就监视哪个属性

  • watchEffect有点像computed:

computed注重的计算出来的值(回调函数的返回值),所以必须要写返回值

而watchEffect更注重的是过程(回调函数的函数体),所以不用写返回值

watchEffect(()=>{
        // 只要被用到的值,都会开启自动监听
          const x1 = sum.value
          const x2 = person.job.job1.salary
          console.log('watchEffect被触发了')
    })
Vue3的生命周期

beforeCreate

created

beforeMount

mounted

beforeUpdate

updated

beforeUnmount

unmounted

Demo组件

<template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button>
</template>

<script>
import {ref,watchEffect,reactive} from "vue";
export default {
  name: "Demo",
  setup(){
   let sum = ref(0)

    return{
      sum,
    }
  },
  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---')
  }
}
</script>

<style scoped>

</style>

App.vue

<template>
  <Demo v-if="isShowDemo"></Demo>
  <br>
  <br>
  <button @click="isShowDemo = !isShowDemo">点击显示/隐藏</button>
</template>

<script>
import {ref} from 'vue'
import Demo from "./components/Demo.vue";
export default {
  name: 'App',
  components:{Demo},
  setup(){
    let isShowDemo = ref(true)

    return{
      isShowDemo
    }
  }
}
</script>
组合式api的生命周期钩子函数的写法

Vue3也提供了组合api形式的生命周期钩子,与Vue2中钩子对应关系如下:

说明:组合式api的方式,就是把这些生命周期钩子函数全部集成到setup()中

beforeCreated ====> setup()

created ====> setup()

beforeMount =====> onBeforeMount

mounted ====> onMounted

beforeUpdate ====> onBeforeUpdate

updated ====> onUpdated

beforeUnmount ====> onBeforeUnmount

unmounted ====> onUnmounted

Demo组件

<template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button>
</template>

<script>
import {ref,watchEffect,reactive,onBeforeMount,onMounted,onBeforeUpdate,onUpdated,onBeforeUnmount,onUnmounted} from "vue";
export default {
  name: "Demo",
  setup(){
   let sum = ref(0)
    console.log('---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{sum,}
  },

  // 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---')
  // }
}
</script>

<style scoped>

</style>

attention:

使用组合api中的钩子函数,触发比配置项快

自定义hook函数
  • 什么是hook?——本质是一个函数,把setup函数中使用的组合式api进行了封装

  • 类似于vue2中的mixin

  • 自定义hook的优势:复用代码,让setup中的逻辑更清楚易懂

案例:鼠标点击获取鼠标所点击的坐标

Demo组件

<template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button>
  <hr>
  <h2>当前的坐标为x:{{position.x}} y:{{position.y}}</h2>
</template>

<script>
import {ref, reactive, onMounted, onBeforeUnmount} from "vue";
export default {
  name: "Demo",
  setup(){
   let sum = ref(0)
    let position = reactive({
      x:0,
      y:0
    })

    const getPosition = (e)=>{
     position.x = e.pageX
      position.y =e.pageY
     console.log(e.pageX,e.pageY)
    }

    onMounted(()=>{
      window.addEventListener('click',getPosition)
    })

    onBeforeUnmount(()=>{
      window.removeEventListener('click',getPosition)
    })




    return{sum,position}
  },
}
</script>

<style scoped>

</style>

使用hooks

在src目录下新建一个hooks文件夹,里面定义一个js文件

import {ref, reactive, onMounted, onBeforeUnmount} from "vue";
export function usePosition() {
    let position = reactive({
        x:0,
        y:0
    })

    const getPosition = (e)=>{
        position.x = e.pageX
        position.y =e.pageY
        console.log(e.pageX,e.pageY)
    }

    onMounted(()=>{
        window.addEventListener('click',getPosition)
    })

    onBeforeUnmount(()=>{
        window.removeEventListener('click',getPosition)
    })

//这里一定要有return值
    return position
}

在组件中,对hooks进行引用,之后使用值去接收函数传递的值

<template>
   <h2>当前求和为:{{sum}}</h2>
   <button @click="sum++">点我+1</button>
  <hr>
  <h2>当前的坐标为x:{{position.x}} y:{{position.y}}</h2>
</template>

<script>
import {ref, reactive, onMounted, onBeforeUnmount} from "vue";
import {usePosition} from "../hooks/usePosition";
export default {
  name: "Demo",
  setup(){
   let sum = ref(0)
   const position = usePosition()

    return{sum,position}
  },
}
</script>

<style scoped>

</style>
toRef

将一个对象中的某个属性,变成响应式数据

Demo组件

<template>
  <h2>姓名:{{person.name}}</h2>
  <h2>年龄:{{person.age}}</h2>
  <h2>职业:{{person.job.job1.title}}</h2>
  <h2>薪水:{{person.job.job1.salary}}K</h2>
  <button @click="person.name+='~'">修改姓名</button>
  <br>
  <br>
  <button @click="person.age++">增长年龄</button>
  <br>
  <br>
  <button @click="person.job.job1.salary++">涨薪</button>
  <br>
  <br>
</template>

<script>
import {ref,toRef,reactive} from "vue";
export default {
  name: "Demo",
  setup(){
    let person = reactive({
      name:'joy',
      age:23,
      job:{
        job1:{
          title:'前端开发工程师',
          salary:30
        }
      }
    })



    return{
      person,
      name:toRef(person,'name'),
      age:toRef(person,'age'),
      title: toRef(person.job.job1,'title'),
      salary: toRef(person.job.job1,'salary')
    }
  }
}
</script>

<style scoped>

</style>
toRefs

把一个对象中的所有属性,都变成响应式数据

<template>
  <h2>姓名:{{person.name}}</h2>
  <h2>年龄:{{person.age}}</h2>
  <h2>职业:{{person.job.job1.title}}</h2>
  <h2>薪水:{{person.job.job1.salary}}K</h2>
  <button @click="person.name+='~'">修改姓名</button>
  <br>
  <br>
  <button @click="person.age++">增长年龄</button>
  <br>
  <br>
  <button @click="person.job.job1.salary++">涨薪</button>
  <br>
  <br>
</template>

<script>
import {ref,toRef,reactive,toRefs} from "vue";
export default {
  name: "Demo",
  setup(){
    let person = reactive({
      name:'joy',
      age:23,
      job:{
        job1:{
          title:'前端开发工程师',
          salary:30
        }
      }
    })

    const x = toRefs(person)
    console.log('x=======',x)

    return{
      person,
      name:toRef(person,'name'),
      age:toRef(person,'age'),
      title: toRef(person.job.job1,'title'),
      salary: toRef(person.job.job1,'salary')
    }
  }
}
</script>

<style scoped>

</style>

其他组合式api

shallowReactive和shallowRef
readonly与shallowReadonly
toRaw与markRaw
customRef
provide与inject

响应式数据的判断

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

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

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

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

<template>
  <Demo v-if="isShowDemo"></Demo>
  <br>
  <br>
  <button @click="isShowDemo = !isShowDemo">点击显示/隐藏</button>
</template>

<script>
import {ref,reactive,readonly,isRef,isReactive,isReadonly,isProxy} from 'vue'
import Demo from "./components/Demo.vue";
export default {
  name: 'App',
  components:{Demo},
  setup(){
    let isShowDemo = ref(true)
    let car = reactive({name:'nio',price:'40w'})
    let carCopy = readonly(car)

    console.log(isRef(isShowDemo)) //true
    console.log(isReactive(car))  //true
    console.log(isProxy(car))   //true
    console.log(isReadonly(carCopy))   //true
     console.log(isProxy(carCopy))   //true
    //readonly只设置成只读,并不改变car的Proxy属性

    return{isShowDemo,car,carCopy}
  }
}
</script>

Composition api的优势

option api存在的问题

在传统OptionAPI中,新增或者修改一个需求,就需要分别在data,methods,computed里修改

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3bNbfkGr-1644217195480)(file:///Users/joy.zhang1/Library/Application%20Support/marktext/images/2022-02-02-11-20-47-image.png)]

Composition API的优势

我们可以更加优雅的组织我们的代码,函数。让相关功能的代码更加有序的组织在一起

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fL9kDCQ7-1644217195481)(file:///Users/joy.zhang1/Library/Application%20Support/marktext/images/2022-02-02-11-19-48-image.png)]

新的组件

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

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

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

Teleport
Suspense

目前还处于实验阶段

其他

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值