Skip to content
本页目录

Vue2 与 Vue3 的区别

1. 生命周期

  1. beforeDestorydestoryed 名称的改变
  2. 在组合 API 中没有 beforeCreatecreated 生命周期,而是使用 setup 方法

2. 多根节点

组件中不需要在最外层写个标签将内容包裹

3. Composition API

Vue2 选项式 APIOptions Api),一个逻辑会分散在不同的属性中,如data、props、computed、watch、method、生命周期,导致代码可读性差,书写与维护困难。

Vue3 组合式 APIComposition API),很好地解决了这个问题,可将同一逻辑的内容写到一起,增强了代码的可读性、内聚性,其还提供了较为完美的逻辑复用性方案。

优点:

  • 更好的逻辑复用
  • 更灵活的代码组织
  • 更好的类型推导
  • 更小的生产包体积

组合式 API:

  • 响应式 API:
    • ref():把基本类型的数据变为响应式数据。接受一个参数值并返回一个响应式且可改变的 ref 对象。ref 对象拥有一个指向内部值的单一属性 .value,如果传入的是一个引用类型,会在内部调用 reactive() 去处理。
    • reactive():将引用类型数据转为响应式数据,参数必须是对象或数组
  • 生命周期
    • setup 中没有 beforeCreatecreated 生命周期
    • setup 中使用生命周期需要加上前缀 on,如 onMounted
    • setupbeforeCreate、created 生命周期更早,也就是说在当前直接用 this 去获取 data 中的数据打出来的还是 undefined
JS
import { ref, toRefs, toRef  } from 'vue'

export default {
  props: {
    title: String
  },

  // 注意 props 不能解构,否则丢失响应性
  setup(props, context) {
    // 将 `props` 转为一个其中全是 ref 的对象,然后解构
    const { title } = toRefs(props)
    // `title` 是一个追踪着 `props.title` 的 ref
    console.log(title.value)

    // 或者,将 `props` 的单个属性转为一个 ref
    const title = toRef(props, 'title')
    
    const count = ref(0)  // count.value = 0

    onMounted(()=>{
      const posts = await res.json()
   })


    return {
      count,
      posts
    }
  },

  mounted() {
    console.log(this.count)
  }
}
import { ref, toRefs, toRef  } from 'vue'

export default {
  props: {
    title: String
  },

  // 注意 props 不能解构,否则丢失响应性
  setup(props, context) {
    // 将 `props` 转为一个其中全是 ref 的对象,然后解构
    const { title } = toRefs(props)
    // `title` 是一个追踪着 `props.title` 的 ref
    console.log(title.value)

    // 或者,将 `props` 的单个属性转为一个 ref
    const title = toRef(props, 'title')
    
    const count = ref(0)  // count.value = 0

    onMounted(()=>{
      const posts = await res.json()
   })


    return {
      count,
      posts
    }
  },

  mounted() {
    console.log(this.count)
  }
}

Setup 上下文,上下文对象暴露了其他一些在 setup 中可能会用到的值:

JS
export default {
  setup(props, context) {
    // 透传 Attributes(非响应式的对象,等价于 $attrs)
    console.log(context.attrs)

    // 插槽(非响应式的对象,等价于 $slots)
    console.log(context.slots)

    // 触发事件(函数,等价于 $emit)
    console.log(context.emit)

    // 暴露公共属性(函数)
    console.log(context.expose)
  }
}
export default {
  setup(props, context) {
    // 透传 Attributes(非响应式的对象,等价于 $attrs)
    console.log(context.attrs)

    // 插槽(非响应式的对象,等价于 $slots)
    console.log(context.slots)

    // 触发事件(函数,等价于 $emit)
    console.log(context.emit)

    // 暴露公共属性(函数)
    console.log(context.expose)
  }
}

该上下文对象是非响应式的,可以安全地解构:

JS
export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}
export default {
  setup(props, { attrs, slots, emit, expose }) {
    ...
  }
}

watchEffect()

js
watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})
watchEffect(() => {
  // 用来做响应性追踪
  console.log(copy.count)
})

computed

4. 异步组件 Suspense

5. 内置组件 Teleport

该组件可以将其所在组件模板的部分内容移动到指定的 DOM 位置,比如 body 或者其他任意位置,而 Vue2 中只能通过 $el 操作 DOM 方式实现。

场景:modal、toast 等需要插入到 body 中的组件

参数:

  • to:需要移动的位置,选择器或者 DOM 节点,内部会使用 querySelector 方法查找该节点
  • disabled:是否禁用 to 功能
HTML
<teleport :to="body" :disabled="false">
  <div>需要移动的内容</div>
</teleport>
<teleport :to="body" :disabled="false">
  <div>需要移动的内容</div>
</teleport>

注意点:

  • 其组件样式不会受被挂载的父组件样式影响(因为其 dom 已不在父级下)
  • 父级无法捕捉 Teleport 节点的事件冒泡(同上)
  • 多个 teleport 在同一个目标元素时会将内容追加到目标元素

6. 响应式原理

  • Vue2 响应式原理基础是 Object.defineProperty,需要劫持每个属性,所以对新增以及数组索引的变更无法监听
  • Vue3 响应式原理基础是 Proxy,监听的是整个对象,所以能知道对象的每个属性变更

Object.defineProperty 问题:

  • 无法监听对象或数组的新增、删除元素操作,主要原因是无法监听 .length,它无法被劫持监听且修改

解决方案:

  • push、pop、shift、unshift、splice、sort、reverse 方法进行重写
  • 提供 Vue.set 方法新增或删除对象属性或数组元素

以上方法是在其方法内部调用了对应的 dep.notify() 方法触发视图层的更新

7. 虚拟 DOM

Vue3 相比于 Vue2,虚拟 DOM 上增加 patchFlag 字段

9. diff 算法优化

依赖 babel 编译出来的静态节点提升了 Diff 性能

10. 打包优化 Tree-Shaking

Tree-Shaking 是一种基于 ES Module 规范的 Dead Code Elimination 技术,它会在运行过程中静态分析模块之间的导入导出,确定 ESM 模块中哪些导出值未曾其它模块使用,并将其删除,以此实现打包产物的优化。

Vue3 中全局 API 只能通过 ES 模块显示导入进行访问,所以 webpack 可以检测代码模块是否被导出、导入,且被 JavaScript 文件使用。

内置 API 比如 transition、v-model 等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。Vue3 将所有运行功能打包也只有约22.5kb,比 Vue2 轻量很多。

8. 事件缓存

Vue3cacheHandler 可在第一次渲染后缓存我们的事件。相比于 Vue2 无需每次渲染都传递一个新函数。

11. TypeScript 支持

12. 全局 API 优化

全局 API 只能作为 ES 模块构建的命名导出进行访问。

JS
import { nextTick } from 'vue'
nextTick(() => {
  // 一些和DOM有关的东西
})
import { nextTick } from 'vue'
nextTick(() => {
  // 一些和DOM有关的东西
})

13. router

vue2 中使用 this.$router.push() 等方法进行路由跳转,vue3 中如下:

JS
import { useRouter, useRoute } from "vue-router"

setup(){
  const router = useRouter()
  const route = useRoute()
  function fn(){
    router.push('/about')
  }
  onMounted(()=>{
    console.log(route.query.code)
  })
  return{
    fn
  }
}
import { useRouter, useRoute } from "vue-router"

setup(){
  const router = useRouter()
  const route = useRoute()
  function fn(){
    router.push('/about')
  }
  onMounted(()=>{
    console.log(route.query.code)
  })
  return{
    fn
  }
}

方法的使用上一致,只是需要导入对应的方法。
新增了两个方法:

  • router.hasRoute():检查路由是否存在。
  • router.getRoutes():获取一个包含所有路由记录的数组。

14. 全局 API 转移

vue2 中可以通过 Vue.prototype 去操作原型,在 vue3 中只能通过 app.config.globalProperties

2.x 全局 API( Vue)3.x 实例 API(app)
Vue.config.xxxxapp.config.xxxx
Vue.config.productionTip移除
Vue.componentapp.component
Vue.directiveapp.directive
Vue.mixinapp.mixin
Vue.useapp.use
Vue.prototypeapp.config.globalProperties

15. 响应式判断

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

16. 指令与插槽

  1. v-ifv-for 的使用问题
  2. Vue3 移除了 filter (用方法调用或计算属性替换过滤器)
  3. vue3 移除 v-on.native 修饰符

指令生命周期变更

Vue2Vue3
bindbeforeMount
insertedmounted
updatebeforeUpdate
componentUpdatedupdated
-beforeUnmount
unbindunmounted

17. 移除了一些属性与方法

  • $listeners:将 $listeners 事件内容合并到了属性传递的 $attrs
  • $scopedSlots:将其统一到 $slots
  • v-on:native:增加 emits 属性用于定义需要触发的事件

Vue3 优点

  • 性能提升
    • 打包体积减少 41%
    • 初次渲染快 55%,更新快 133%
    • 内存使用减少 54%
    • Composition API 实现逻辑模块化和复用
    • 增加了 Teleport 组件
    • 可以多个根元素
    • 对数组的原生操作