Vue3基础

126 阅读9分钟

第一章 Vue3简介

1. Vue3概述

在2020年9月18日Vue.js发布3.0版本,代号为One Piece (海贼王)。

特点:

1)性能的提升

  • 打包大小减少41%
  • 初次渲染提速55%,更新渲染提速133%
  • 内存占用减少54%

2)源码的升级

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

3)拥抱TypeScript

  • Vue3可以更好的支持TypeScript

4)新特性

  • Composition API(组合API):setup配置、ref与reactive、watch与wachEffect、provide与inject
  • 新的内置组件:Fragment、Teleport、Suspense
  • 其他改变:新的生命周期钩子、data 选项应始终被声明为一个函数、移除keyCode支持作为 v-on 的修饰符

2. 创建Vue3工程

  1. 使用 vue-ci 创建
// 安装或升级@vue/cli
npm install -g @vue/cli
// 创建
vue create vue test
// 启动
npm run serve
  1. 使用 vite 创建

vite:新一代前端构建工具

特点:开发环境中无需打包操作,可快速的冷启动;轻量快速的热重载(HMR) ;按需编译

// 创建工程
npm init vite-app
// 安装依赖
npm install
// 运行
npm run dev

第二章 Composition API

1. setup函数

概念:Vue3.0中一个新的配置项,值为一个函数。所有的Composition API (组合API)、数据、方法等均须在setup编写

返回值:

  • 对象:对象中的属性、方法在模板中均可以直接使用
  • 渲染函数:可以自定义染内容

执行的时机:

  • 在beforeCreate之前执行一次,this指向undefined

接收参数:

  • props:对象类型,包含组件外部传递过来的数据,且在组件内部声明接收了的属性
  • context:上下文对象
<Demo msg="你好啊" school="尚佳谷"/>

props:['msg','school'],
setup(props,context){
   
}
  • 一个 setup 函数的 props 是响应式的,并且会在传入新的 props 时同步更新。
  • 如果解构props对象,解构出的变量将会丢失响应性。推荐通过 props.xxx 的形式来使用其中的 props。如果确实需要解构 props 对象,或者需要将某个 prop 传到一个外部函数中并保持响应性,可以使用 toRefs() 和 toRef() 这两个工具函数

context包含内容:

  • attrs:值为对象,包含组件外部传递过来,但没有在props配置中声明的属性,相当于 this.$attrs
  • slots:接收到的插槽内容,相当于 this.$slots
  • emit:分发自定义事件的函数,相当于 this.$emit

注意:

  • 尽量不要与Vue2.x配置混用,Vue2.x配置(data、methos、computed...) 中可以访问到setup中的属性、方法,但在setup中不能访问到Vue2.x配置 (data、methos、computed...),如果重名则setup优先
  • setup不能是一个async函数,因为返回值不再是retum的对象,而是promise,模板看不到return对象中的属性
// 创建setup
{
   name:'App',
   setup(){
      // 数据
      let name = ref("张);
      let age = ref(18);
      let job = ref({
         type:"前端工程师",
         salary: '30k',
      });
   //方法
   function changeInfo(){
      name.value = '李四';
      age.value = 48
      job.value.type = 'UI设计师'
      job.value.salary = '60k'
   }
   return{
      name,
      age,
      job,
      changeInfo
   }
// 使用导出数据
<h2>姓名: {{name}}</h2>
<h2>年龄: {{lage}}</h2>
<h3>工作种类: {{job.type}}</h3>
<h3>工作薪水: {{job.salary}}</h3>
<button @click="changeInfo">修改人的信息</button>

2. ref函数

作用:定义一个响应式的数据

const xxx = ref(initValue)

ref会创建一个包含响应式数据的引用对象 (reference对象,简称ref对象)

JS中操作数据响应式数据:

xxx.value

模板中读取数据:

<div>{{xxx}}</div>

特点:

  • 接收的数据可以是基本类型、也可以是对象类型
  • 基本类型的数据响应式依然是靠 object.defineProperty()的 get 与 set 完成的
  • 对象类型的数据响应式依靠Vue3.0中的reactive函数实现

3. reactive函数

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

const 代理对象 = rective(源对象) // 接收一个对象(或组),返回一个代理对象 (proxy对象)

特点:

  • reactive定义的响应式数据是"深层次的”
  • 内部基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据
// 创建reactive响应式数据
let job = reactive({
   type:'前端工程师',
   salary: '3ok',
   a:{
      b:{
         c:666
      }
   }
});
let hobby = reactive([抽烟",喝酒,烫头']);
// 操作数据
function changeInfo(){
   job.type = 'UI设计师';
   job.salary = '60k';
   job.a.b.c = 999;
   hobby[o] = '学习';
}

reactive对比ref:

1)定义数据角度

  • ref定义基本类型数据
  • reactive定义对象或数组

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

2)从原理角度

  • ref通过 object.defineProperty()的 get 与 set 来实现响应式 (数据劫持)
  • reactive通过 Proxy 实现响应式 (数据劫持),并通过Reflect操作源对象内部的数据

3)从使用角度

  • ref:操作数据需要 value,读取数据时模板中直接读取不需要 value 
  • reactive:操作数据与读取数据均不需要 value

4. Vue2.0与Vue3.0响应式区别

  1. vue2.x的响应式

实现原理:

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

存在问题:

  • 对象新增和删除某个属性时视图不会更新
  • 直接通过下标修改数组元素时视图不会更新
  1. Vue3.x的响应式

实现原理:

  • Proxy通过(代理)拦截对象中任意属性的变化,包括属性值的读写、属性的添加、属性的删除等
  • 通过Reflect (反射)对被代理对象的属性进行操作

模拟实现:

const p = new Proxy(person,{
   //有人读取p的某个属性时调用
   get(target,propName){
     return target[propName];
   }
   //有人修改p的某个属性或给p追加某个属性时调用
   set(target,propName,value){
      target[propName] = value;
   }
   //有人刷除的某个属性时调用
   deleteProperty(target,propName){
      return delete target[propName];
   }
});

5. 计算属性与监视

  1. computed函数

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

import { computed } from 'vue';

setup(){
   //计算属性一简写形式
   let fulIName = 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];
      }
   });
}
  1. watch函数

Vue3.x中的watch与Vue2.x中配置功能一致

注意事项:

  • 监视reactive定义的响应式数据时oldValue无法正确获取
  • 监视reactive定义的响应式数据整体时强制开启了深度监视 (deep配置失效)
  • 监视reactive定义的响应式数据中某个属性时deep配置有效

情况一:监视ref定义的响应式数据

watch(
   sum, // ref数据
   (newValue,oldValue)=>{ console.log('sum变化了',newValue,oldValue)}, // 回调函数
   { immediate: true } // 配置项
);

情况二:监视多个ref定义的响应式数据

watch(
   [sum,msg], // 多个ref数据
   (newValue ,oldValue)=>{console.log('sum或msg变化了',newValue,oldValue)} // 回调函数
);

情况三:监视reactive定义的响应式数据

watch(
   person, // reactive对象
   (newValue,oldValue)=>{ console.log('person变化了',newValue,oldValue)}, // 回调函数
   { immediate:true, deep:false } // 配置项,此处的deep配置不再奏效
)

情况四:监视reactive所定义的一个响应式数据中的某个属性

watch(
   ()=>person.name, // 返回值为reactive对象某个属性
   (newValue,oldValue)=>{console.log('person的name变化了',newValue,oldValue)} // 回调函数
)

情况五:监视reactive所定义的一个响应式数据中的某些属性

watch(
   [()=>person.name,()=>person,age], // 某些reactive对象属性组成的数组
   (newvalue,oldValue)=>{console.log('person的name或age变化了',newValue,oldValue)} // 回调函数
)

特殊情况

watch(
   ()=>person.job, // 返回值为reactive对象某个属性
   (newValue,oldValue)=>{console.log('person的job变化了',newValue,oldValue)}, // 回调函数
   { deep:true } // 配置项,此处的deep配置生效
)

6. watchEffect函数

  1. watch与watchEffect区别

watch监视原理:既要指明监视的属性,也要指明监视的回调

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

  1. watchEffect与computed区别

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

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

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

import {watchEffect} from 'vue';

watchEffect(()=>{
   const x1 = sum.value
   const x2 = person.age
   console.log('watchEffect配置的回调执行了')
})

7. 生命周期钩子函数

  1. Vue3.0中可以继续使用Vue2.x中的生命周期钩子,但有有两个被更名
  • beforeDestroy 改名为 beforeUnmount
  • destroyed 改名为 unmounted
  1. Vue3.0也提供了 Composition API 形式的生命周期钩子
  • beforeCreate ===> setup( )
  • created ===> setup()
  • beforeMount ===> onBeforeMount
  • mounted ===>onMounted
  • beforeUpdate ===> onBeforeUpdate
  • updated ===> onUpdated
  • beforeUnmount ===> onBeforeUnmount
  • unmounted ===> onUnmounted

8. toRef和toRefs 

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

// 创建
const name = toRef( person, 'name')

// 导出
return {
   person,
   name:ref(person.name),
   age:ref(person.age),
   salary:ref(person.job.j1.salary),
}
// 使用
<div>{{name}}</div>

适用场景:将响应式对象中的某个属性单独提供给外部

toRefs 与 toRef 功能一致,可以批量创建多个ref对象

// 创建
const per = toRefs(person);

// 导出
return{
   ...toRefs(person)
}

第三章 其它 Composition API

1. shallowReactive 与 shallowRef

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

shallowRef:只处理基本数据类型的响应式,不进行对象的响应式处理

适用场景:

  • shallowReactive:一个对象数据结构比较深,但只有最外层属性会发生变化
  • shallowRef:一个对象中所有属性都不会发生变化,而是生成新的对象来代替原有对象

2. readonly 与 shallowReadonly

  • readonly:使一个响应式数据变为只读的 (深只读)
  • shallowReadonly:使一个响应式数据变为只读的 (浅只读)

适用场景:不希望数据被修改时使用

3. toRaw 与 markRaw

  1. toRaw

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

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

  1. markRaw

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

应用场景:

  • 有些值不应被设置为响应式的,例如复杂的第三方类库等
  • 当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能

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(){
         //自定义一个myRef
         function myRef(value,delay){
            let timer
            return customRef((track,trigger)=>{
               return{
                  get(){track()},
                  set(newValue){
                      clearTimeout(timer)
                      timer = setTimeout(()=> value = newValuetrigger(),3000);
                  }
               }
            }
         }
         let keyWord = myRef("hello",50) //使用程序员自定义的ref
      }
   }

5. provide 与inject

作用:实现祖孙组件间通信

实现原理:父组件有一个 provide 选项提供数据,子组件有一个 inject 选项使用数据

// 祖组件
setup(){
   let car = reactive({name:'奔驰',price:'40万'});
    provide('car',car);
   ......
}
// 孙组件
setup(props,context){
   const car = inject('car');
   return {
     car
   }
}

6. 响应式数据的判断

  • isRef:检查一个值是否为一个 ref 对象
  • isReactive:检查一个对象是否是由reactive创建的响应式代理
  • isReadonly:检查一个对象是否是由readonly 创建的只读代理
  • isProxy:检查一个对象是否是由reactive 或者 readonly 方法创建的代理

7. Composition API的优势

Options API 存在的问题:在传统OptionsAPI中新增或者修改一个需求,就需要分别在data,methods,computed里修改

Composition API 的优势:更加优雅的组织代码,让相关功能的代码更加有序的组织在一起

第四章 新的组件

1. Fragment

在Vue2中组件必须有一个根标签,但是在Vue3中组件可以没有根标签,Vue内部会将多个标签包含在一个Fragment虚拟元素中。达到减少标签层级及内存占用的优点。

2. Teleport

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

<teleport to="移动位置"> // to属性指向内部html结构添加的位置
   <div v-if="isShow" class="mask">
      <div class="dialog">
         <h3>我是一个弹窗</h3>
         <button @click="isShow = false>关闭弹窗</button>
      </div>
  </div>
</teleport>

3. 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/> // Child加载完成显示
          </template>
          <template v-slot:fallback>
             <h3>加载中...</h3> // Child加载中显示
          </template>
      </Suspense>
   </div>
</template>

第五章 其他调整

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 ) 上

Vue.config.xxx ===> app.config.xxx

Vue.config.productionTip ===> 移除

Vue.component ===> app.component

Vue.directive ===> app.directive

Vue.mixin ===> app.mixin

Vue.use ===> app.use

Vue.prototype ===> app.config.global.Properties

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 修饰符

// 父组件中绑定事件
<my-component 
   v-on:close="handleComponentEvent" // 自定义事件,接收
   v-on:click="handleNativeClickEvent"  // 原生事件,不接收
/>

// 子组件中声明自定义事件
<script>
   export default {
      emits: ['close']
   }
</script>

5)移除过滤器 filter