Vue3  
 
Vue3 的变化 
性能的提升
- 打包大小减少 41%
- 初次渲染快 55%, 更新渲染快 133%
- 内存减少 54%
源码的升级
- 使用 Proxy 代替 defineProperty 实现响应式
- 重写虚拟 DOM 的实现和 Tree-Shaking
拥抱 TypeScript
- Vue3 可以更好的支持 TypeScript
新的特性
- Composition API(组合 API)
- setup 配置
- ref 与 reactive
- watch 与 watchEffect
- provide 与 inject
- ......
- 新的内置组件
- Fragment
- Teleport
- Suspense
- 其他改变
- 新的生命周期钩子
- data 选项应始终被声明为一个函数
- 移除 keyCode 支持作为 v-on 的修饰符
- ......
创建 Vue3 工程 
vite 和 vue-cli 对比 
| vite | vue-cli | |
|---|---|---|
| 支持的 vue 版本 | 仅支持 vue3.x | 支持 2.x 和 3.x | 
| 是否基于 webpack | 否 | 是 | 
| 运行速度 | 快 | 较慢 | 
| 功能完整度 | 小而巧 | 大而全 | 
| 是否建议企业级开发使用 | 暂不建议 | 建议 | 
使用 vue-cli 创建 
### 查看 @vue/cli 版本,确保 @vue/cli 版本在4.5.0以上
vue --version
### 安装或者升级 @vue/cli
npm install -g @vue/cli
### 创建
vue create vue_test
### 启动
cd vue_test
npm run serve使用 vite 创建 
- vite:新一代前端构建工具
- 优势: - 开发环境中,无需打包操作,可快速冷启动(webpack 每次运行项目都要打包)
- 轻量快速的热重载 HMR(更改代码局部刷新,webpack 也行,但 vite 更轻量)
- 真正的按需编译,无需等待整个应用编译完成
 
- 传统构建 与 vite 构建对比(vite 现用现分析,按需导入,因此项目启动更快)


npm init vite-app 项目名称
cd 项目名称
npm install
npm run devVue3 项目结构 
Vue3 中 main.js 代码有所改变:
// 不再引入 Vue 构造函数,而是引入 createApp 工厂函数
// createApp函数:创建 vue 的 SPA 实例
import { createApp } from 'vue'
import App from './App.vue'
// 创建应用实例对象
const app = createApp(App)
app.mount('#app')Vue3 支持定义多个根节点,组件的 <template> 支持定义多个根节点:
<template>
  <h1>根节点</h1>
  <h1>根节点</h1>
</template>常用 Composition API 
setup 
- setup 是 Vue3 中一个新的配置项,值为函数
- 组件中使用的数据、方法等都要配置在 setup 中
- setup 函数两种返回值: - 返回一个对象,对象中的属性、方法可在模板中直接使用
- 返回一个渲染函数,可自定义渲染内容
 
- setup 函数的参数: - props:值为对象,包含了组件外部传进来,且组件内部声明接收的属性
- context:上下文对象 - attrs:值为对象,包含了组件外部传进来,且组件内部没有声明接收的属性,相当于- this.$attrs
- slots:收到的插槽内容,相当于- this.$slots
- emit:触发自定义事件的函数,相当于- this.$emit
 
 
// 没错,渲染函数就叫 h
import { h } from 'vue'
export default {
  name: 'App',
  props: ['title'],
  // Vue3 需要声明自定义事件,虽然不声明也能运行
  emits: ['changeCount'],
  // 返回函数
  /*
  setup() {
    return () => h('h1', 'Hello')
  },
  */
  // 返回对象
  setup(props, context) {
    let name = 'Vue3'
    function sayHello() {}
    function test() {
      context.emit('changeCount', 888)
    }
    return {
      name,
      sayHello,
      test,
    }
  },
}注意:
- setup 在 beforeCreate钩子之前执行,this为undefined
- setup 不要和 Vue2 配置混用。Vue2 的配置可以访问到 setup 的属性方法,反过来不行;如有重名,setup 优先
- setup 不能是 async 函数,因为 async 函数返回的是 promise 不是对象,会导致模板无法访问属性方法
- 若要返回 promise 实例,需要 Suspense和异步组件的配合
ref 函数 
作用:定义响应式数据
语法:const name = ref(initValue)
- ref函数返回一个- RefImpl(reference implement) 实例对象,全称引用实现的实例对象
- 它包含响应式数据,简称引用对象、reference 对象、ref 对象
- JS 访问数据:name.value
- 模板访问数据:<div></div>
注意事项:
- ref函数可以接收基本数据类型和引用数据类型
- 基本类型数据的响应式还是靠 Object.defineProperty()完成
- 对象类型数据使用 ES6 的 Proxy 实现响应式,Vue3 把相关操作封装在 reactive函数中
- 按照之前的办法,对于对象数据,应该遍历每一层的属性添加 getter、setter,但 Vue3 使用 Proxy 把内部数据一口气监测了
<h2>{{ name }}</h2>
<p>{{ jobInfo.type }}</p>import { ref } from 'vue'
export default {
  setup() {
    let name = ref('Vue3')
    let jobInfo = ref({
      type: 'frontend',
      salary: '40w',
    })
    function changeInfo() {
      name.value = '鱿鱼丝'
      // jobInfo 是 RefImpl 实例
      // jobInfo.value 是 Proxy 实例对象
      jobInfo.value.salary = '50w'
    }
    return {
      name,
      jobInfo,
      changeInfo,
    }
  },
}reactive 函数 
- 定义引用类型的响应式数据,不可用于 jibenleixingshuju
- const 代理对象 = reactive(源对象)接收对象或数组,返回代理对象(Proxy 的实例对象)
- reactive的响应式是深度的
- 基于 ES6 的 Proxy 实现,通过代理对象操作源对象内部数据
import { reactive } from 'vue'
export default {
  setup() {
    let person = reactive({
      name: 'Vue3',
      sex: 'unknown',
      info: {
        school: 'Oxford',
        major: 'computer',
      },
    })
    let color = reactive(['red', 'green', 'blue'])
    function changeInfo() {
      person.info.major = 'art'
      color[0] = 'yellow'
    }
    return {
      person,
      color,
      changeInfo,
    }
  },
}ref VS reactive 
定义数据:
- ref 用于定义基本类型数据
- reactive 用于定义对象或数组类型数据
- ref 也可定义对象或数组类型数据,内部通过 reactive 转为代理对象
- 一般使用 reactive 函数,可以把所有数据封装为一个对象
原理:
- ref 通过 Object.defineProperty()实现响应式
- reactive 通过 Proxy 实现响应式,Reflect 操作源对象数据
使用角度:
- ref 定义数据,访问数据需要 .value,模板中不需要
- reactive 定义的数据,都不需要
Vue3 响应式原理 
let originPerson = {
  name: 'Lily',
  age: 22,
}
let person = new Proxy(originPerson, {
  // 拦截增加和查询操作
  get(target, prop) {
    // 读取源对象的属性
    return Reflect.get(originPerson, prop)
  },
  // 拦截修改操作
  set(target, prop, value) {
    // 修改源对象的属性
    return Reflect.set(target, prop, value)
  },
  // 拦截删除操作
  deleteProperty(target, prop) {
    // 删除源对象的属性
    return Reflect.deleteProperty(target, prop)
  },
})
console.log(person.name)
person.age = 33
person.sex = 'unknown'
delete person.agecomputed 函数 
import { reactive, computed } from 'vue'
export default {
  setup() {
    let person = reactive({
      firstName: 'Cai',
      lastName: 'QP',
    })
    // 计算属性简写形式
    person.fullName = computed(() => {
      return person.firstName + '-' + person.lastName
    })
    // 计算属性完整形式
    person.fullName = computed({
      get() {
        return person.firstName + '-' + person.lastName
      },
      set(value) {
        const arr = value.split('-')
        person.firstName = arr[0]
        person.lastName = arr[1]
      },
    })
    return {
      person,
    }
  },
}watch 函数 
Vue3 watch 能侦听的东西
A watch source can only be a getter/effect function, a ref, a reactive object, or an array of these types
import { ref, reactive, watch } from 'vue'
...
// 数据
let sum = ref(0)
let msg = ref('hello')
let person = reactive({
  name: 'Vue3',
  age: 18,
  info: {
    job: {
      salary: 40,
    },
  },
})侦听 ref 定义的响应式数据:
- 注意不要写成 sum.value
// 参数:侦听的数据,回调,其他配置
watch(
  sum,
  (newVal, oldVal) => {
    console.log(newVal, oldVal)
  },
  { immediate: true }
)侦听多个 ref 定义的响应式数据:
// newVal,oldVal 也是数组
watch([sum, msg], (newVal, oldVal) => {
  console.log(newVal, oldVal)
})侦听 ref 定义的对象类型数据:
// 用 ref 定义对象类型数据
let person = ref({
  name: 'Vue3',
  age: 18,
  info: {
    job: {
      salary: 40,
    },
  },
})
// 开启深度监听才有效,此时监听的是 RefImpl 实例
// Ref 实例的 value 是 Proxy 对象,存的是地址
// 因此无法监听 person 内部属性的变化
watch(person, (newVal, oldVal) => { ... }, { deep:true })
// 这个和 “侦听 reactive 函数直接返回的那一整坨响应式数据” 效果一致
watch(person.value, (newVal, oldVal) => {...})侦听 reactive 函数直接返回的那一整坨响应式数据:
- oldVal 是错误的!和 newVal 的值一样
- 强制开启了深度侦听,deep配置不生效!
watch(
  person,
  (newVal, oldVal) => {
    console.log(newVal, oldVal)
  },
  { immediate: true, deep: false }
)侦听 reactive 定义的响应式数据某个属性:
- 如果是 () => person.infooldVal 也是错误的!
- () => person.nameoldVal 是正确的,何时对何时错自己琢磨吧!
- 此处没有强制开启深度监听
// 如果监视的属性还是对象,则需要开启深度监听
watch(
  () => person.info,
  (newVal, oldVal) => {
    console.log(newVal, oldVal)
  },
  { deep: true }
)侦听 reactive 定义的响应式数据多个属性:
watch(
  [() => person.name, () => person.info],
  (newVal, oldVal) => {
    console.log(newVal, oldVal)
  },
  { deep: true }
)watchEffect 函数 
- watchEffect不需要指明监听哪个属性,回调里用到哪个属性,就自动监听哪个属性
- computed注重计算的值,即回调函数的返回值,因此必须有返回值
- watchEffect更注重过程,即回调函数的函数体,因此可没有返回值
- watchEffect没有开启深度监听,也不能开启深度监听!
- watchEffect内部自行修改数据,不会重新调用回调,因此不会出现递归调用
// 回调中用到的数据只要发生变化,则直接重新执行回调
watchEffect(() => {
  let total = sum.value
  let p = person
  console.log('watchEffect...')
})生命周期 
注意和 vue2.x 的生命周期图作对比,beforeDestroy 和 destroyed 变为 beforeUnmount 和 unmounted 。
Vue3 也提供了 Composition API 形式的生命周期钩子,与 Vue2 中钩子对应关系如下:
- beforeCreate===>- setup()
- created=======>- setup()
- beforeMount===>- onBeforeMount
- mounted=======>- onMounted
- beforeUpdate===>- onBeforeUpdate
- updated=======>- onUpdated
- beforeUnmount==>- onBeforeUnmount
- unmounted=====>- onUnmounted
若和配置项生命钩子一起使用,则组合式会比配置项的先执行,如 onBeforeMount 先于 beforeMount
import { onBeforeMount, onMounted, onBeforeUpdate, onUpdated, onBeforeUnmount, onUnmounted } from 'vue'
setup(){
  console.log('---setup---')
  let sum = ref(0)
  //通过组合式API的形式去使用生命周期钩子
  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}
},hook 函数 
- hook 是一个函数,把 setup 函数的 Composition API 进行了封装
- 类似 Vue2 的 Mixin,能复用代码,让 setup 里的逻辑更清晰
- hook 放在 hooks 文件夹中,一个文件对应一个功能模块,以 useXxx命名
// hooks/usePoint.js
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
}// 使用 hook
import usePoint from '../hooks/usePoint.js'
export default {
  setup() {
    let point = usePoint()
    return { point }
  },
}toRef 函数 
- 创建一个 RefImpl 实例对象,其 value 值指向另一个对象的某个属性,修改 value 值会修改源对象对应的属性
- 应用:需要把响应式对象的某个属性单独提供给外部使用
- 批量创建:toRefs
import {reactive, toRef, toRefs} from 'vue'
...
setup() {
  let person = reactive({
    name: 'Vue3',
    age: 18,
    info: {
      job: {
        salary: 40,
      },
    },
  })
  return {
    // 注意不能写成 ref(person.name),这和源对象是割裂开的
    name: toRef(person, 'name'),
    salary: toRef(person.info.job, 'salary')
    // or
    ...toRefs(person)
  }
}其它 Composition API 
shallowReactive & shallowRef 
- shallowReactive:只处理对象最外层属性的响应式,即浅响应式
- shallowRef:基本数据类型和- ref相同,对象数据不再会调用- reactive,因此只有对象引用改变了才是响应式的
- 若一个对象数据,结构很深,但只有最外层属性变化,可用 shallowReactive
- 若一个对象数据,属性不会改变,而是使用新对象替换,可用 shallowRef
import { shallowReactive, shallowRef } from 'vue'
setup() {
  let person = shallowReactive({
    name: 'Vue3',
    age: 21,
    info: {
      job: {
        salary: 22
      }
    }
  })
  let x = shallowRef({
    y: 0
  })
  return {
    person,
    x
  }
}readonly & shallowReadonly 
- readonly: 让一个响应式数据变为只读的(深只读)
- shallowReadonly:让一个响应式数据变为只读的(浅只读)
- 应用场景: 不希望数据被修改时,如你用了别人的响应式数据,但是别人不希望你修改时
setup() {
  let sum = ref(0)
  let person = reactive({...})
  sum = readonly(sum)
  person = shallowReadonly(person)
  return {
    sum,
    person
  }
}toRaw & markRaw 
toRaw:
- 将一个由 reactive生成的响应式对象转为普通对象
- 用于读取响应式对象对应的普通对象,对该普通对象的操作不会引起页面更新
markRaw:
- 标记一个对象,让其不成为响应式对象
- 有些值不应设置为响应式,比如复杂的第三方库
- 当渲染复杂且不变的数据时,跳过响应式转换可提高性能
注意:仅仅让数据变为非响应式的,数据变的依旧变,只是没引起页面更新
setup() {
  function showRawPerson() {
    const p = toRaw(person);
    p.age++;
    console.log(p);
    console.log(person);
  }
  function addCar() {
    let car = { name: "奔驰", price: 40 };
    person.car = markRaw(car);
  }
}customRef 
创建一个自定义的 ref,并对其依赖项跟踪和更新触发进行显式控制
<input type="text" v-model="keyword" />
<h3>{{ keyword }}</h3>import { ref, customRef } from 'vue'
export default {
  name: 'Demo',
  setup() {
    // 自定义 myRef
    function myRef(value, delay) {
      let timer
      // 通过customRef去实现自定义
      return customRef((track, trigger) => {
        //
        return {
          get() {
            //告诉Vue这个value值是需要被“追踪”的
            track()
            return value
          },
          set(newValue) {
            clearTimeout(timer)
            timer = setTimeout(() => {
              value = newValue
              // 告诉Vue去更新界面
              trigger()
            }, delay)
          },
        }
      })
    }
    let keyword = myRef('hello', 500)
    return {
      keyword,
    }
  },
}provide / inject 
实现祖先组件与后代组件之间通信。

// 祖先组件传递数据
import { provide, reactive, ref } from 'vue'
setup() {
  let car = reactive({...})
  let sum = ref(0)
  provide('sum', sum)
  provide('car', car)
}// 后代组件接收数据
import { inject } from 'vue'
setup() {
  const car = inject('car')
  const sum = inject('sum')
  return { car, sum }
}响应式数据的判断 
- isRef: 检查一个值是否为一个- ref对象
- isReactive: 检查一个对象是否是由- reactive创建的响应式代理
- isReadonly: 检查一个对象是否是由- readonly创建的只读代理
- isProxy: 检查一个对象是否是由- reactive或者- readonly方法创建的代理
Compositon API 的优势 
Options API 存在的问题
使用传统 Options API 中,新增或者修改一个需求,就需要分别在 data,methods,computed 等地方修改。
Composition API 的优势
可以更加优雅地组织代码、函数,让相关功能的代码更加有序的组织在一起。说白了就是让同一个功能的代码整合到一起,日后修改代码直接找对应的功能模块。
新的组件 
Fragment 
- 在 Vue2 中: 组件必须有一个根标签
- 在 Vue3 中: 组件可以没有根标签, 内部会将多个标签包含在一个 Fragment虚拟元素中
- 好处: 减少标签层级, 减小内存占用
Teleport 
- 将包裹的 HTML 结构移动到指定元素的末尾
- to属性为 CSS 选择器
简易的模态框效果:
<teleport to="#root">
  <div v-if="isShow" class="mask">
    <div class="dialog">
      <h3>我是一个弹窗</h3>
      <button @click="isShow = false">关闭弹窗</button>
    </div>
  </div>
</teleport>.mask {
  /* 遮罩层铺满窗口 */
  position: absolute;
  top: 0;
  bottom: 0;
  left: 0;
  right: 0;
  background-color: rgba(0, 0, 0, 0.5);
}
.dialog {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  text-align: center;
  width: 300px;
  height: 300px;
  background-color: green;
}Suspense 
等待异步组件时渲染额外内容,让用户体验更好
异步引入组件:
import { defineAsyncComponent } from 'vue'
const Child = defineAsyncComponent(() => import('./components/Child.vue'))使用 Suspense 包裹组件,实际上是往插槽填充内容,default 插槽填充组件内容,fallback 插槽填充组件加载时显示的内容:
<Suspense>
  <template v-slot:default>
    <Child />
  </template>
  <template v-slot:fallback>
    <h3>加载中,请稍等...</h3>
  </template>
</Suspense>另外,若 Child 组件的 setup 函数返回一个 Promise 对象,也能渲染 fallback 里的内容:
async setup() {
  let sum = ref(0)
  return await new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve({sum})
    }, 3000)
  })
}其他改变 
- 全局 API 的转移
Vue3 将全局的 API,即:Vue.xxx 调整到应用实例 app 上:
| Vue2 全局 API | Vue3 实例 API | 
|---|---|
| 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.globalProperties | 
- data选项应始终被声明为一个函数
- 过渡类名的更改: 
/* Vue2 */
.v-enter,
.v-leave-to {
  opacity: 0;
}
.v-leave,
.v-enter-to {
  opacity: 1;
}
/* Vue3 */
.v-enter-from,
.v-leave-to {
  opacity: 0;
}
.v-leave-from,
.v-enter-to {
  opacity: 1;
}- 移除 keyCode作为v-on的修饰符,同时也不再支持config.keyCodes
- 移除 v-on.native修饰符,子组件没有在emits: ['close']声明的自定义事件作为原生事件处理
- 移除过滤器 filter
- ...
组件上的 v-model 
当需要维护组件内外数据的同步时,可以在组件上使用 v-model 指令。
父组件传值:
<!-- 父组件传值 -->
<my-counter v-model:number="count"></my-counter>子组件在 emits 节点声明自定义事件,格式为 update:xxx ,调用 $emit 触发自定义事件:
export default {
  props: ['number'],
  emits: ['update:number'],
  methods: {
    add() {
      this.$emit('update:number', this.number++)
    },
  },
}注意,在 vue3 中 props 属性同样是只读的,上面 this.number++ 并没有修改 number 的值。
其实通过 v-bind 传值和监听自定义事件的方式能实现和 v-model 相同的效果。
EventBus 
借助于第三方的包 mitt 来创建 eventBus 对象,从而实现兄弟组件之间的数据共享。
安装 mitt 依赖包:
npm install [email protected]创建公共的 eventBus 模块:
import mitt from 'mitt'
// 创建 EventBus 实例对象
const bus = mitt()
export default bus数据接收方调用 bus.on() 监听自定义事件:
import bus from './eventBus.js'
export default {
  data() {
    return { count: 0 }
  },
  created() {
    bus.on('countChange', (count) => {
      this.count = count
    })
  },
}数据接发送方调用 bus.emit() 触发事件:
import bus from './eventBus.js'
export default {
  data() {
    return { cout: 0 }
  },
  methods: {
    addCount() {
      this.count++
      bus.emit('countChange', this.count)
    },
  },
}vue 3.x 全局配置 axios 
实际项目开发中,几乎每个组件中都会使用 axios 发起数据请求。此时会遇到如下两个问题:
- 每个组件中都需要导入 axios(代码臃肿)
- 每次发请求都需要填写完整的请求路径(不利于后期的维护)
在 main.js 文件中进行配置:
// 配置请求根路径
axios.defaults.baseURL = 'http://api.com'
// 将 axios 挂载为 app 全局自定义属性
// 每个组件可通过 this.$http 访问到 axios
app.config.globalProperties.$http = axios组件调用:
this.$http.get('/users')