Vue3 相比于 Vue2不同的地方

响应式原理

Vue2 响应式原理基础是Object.defineProperty;Vue3 响应式原理基础是Proxy。

Object.defineProperty

基本用法:直接在一个对象上定义新的属性或修改现有的属性,并返回对象。
缺陷: 无法监听对象或数组新增、删除的元素。
Object.defineOProperty是可以监听数组已有元素,但 Vue2 没有提供的原因是性能问题。

Vue2 方案:针对常用数组原型方法push、pop、shift、unshift、splice、sort、reverse进行了hack处理;提供Vue.set监听对象/数组新增属性。对象的新增/删除响应,还可以new个新对象,新增则合并新属性和旧对象;删除则将删除属性后的对象深拷贝给新对象。

Proxy

Proxy是ES6新特性,通过第2个参数handler拦截目标对象的行为。相较于Object.defineProperty提供语言全范围的响应能力,消除了局限性。但在兼容性上放弃了(IE11以下)

局限性

  • 对象/数组的新增、删除。
  • 监测.length修改。
  • Map、Set、WeakMap、WeakSet的支持。

基本用法:创建对象的代理,从而实现基本操作的拦截和自定义操作。

const handler = {
  get: function(obj, prop) {
    return prop in obj ? obj[prop] : ''
  },
  set: function() {},
  ...
}

搬运 Vue3 的源码 reactive.ts 文件

function createReactiveObject(target, isReadOnly, baseHandlers, collectionHandlers, proxyMap) {
  ...
  // collectionHandlers: 处理Map、Set、WeakMap、WeakSet
  // baseHandlers: 处理数组、对象
  const proxy = new Proxy(
    target,
    targetType === TargetType.COLLECTION ? collectionHandlers : baseHandlers
  )
  proxyMap.set(target, proxy)
  return proxy
}

虚拟DOM

Vue3 相比于 Vue2 虚拟DOM 上增加patchFlag字段。
那在 diff 过程中,只需比对文本对容,无需关注 class、style等。除此之外,发现所有的静态节点,都保存为一个变量进行静态提升,可在重新渲染时直接引用,无需重新创建。

事件缓存

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

声明周期

表面语法来看变化不大,只是名字大部分需要 + on,功能上类似。使用上 Vue3 组合式 API 需要先引入;Vue2 选项 API 则可直接调用,如下所示。

// vue3
<script setup>     
import { onMounted } from 'vue'
onMounted(() => {
  ...
})
// 可将不同的逻辑拆开成多个onMounted,依然按顺序执行,不被覆盖
onMounted(() => {
  ...
})
</script>

// vue2
<script>     
   export default {         
      mounted() {             
        ...         
      },           
   }
</script> 
常用的声明周期如下:
Vue2.xVue3
beforeCreateNot needed*
createdNot needed*
beforeMountonBeforeMount
mountedonMounted
beforeUpdateonBeforeUpdate
updatedonUpdated
beforeDestroyonBeforeUnmount
destroyedonUnmounted

注意:setup是围绕beforeCreate和created生命周期钩子运行的,所以不需要特意地去定义。

根节点

Vue2中,编写页面的时候,我们需要去将组件包裹在

中,否则报错警告。

<template>
  <div>
    <header>...</header>
    <main>...</main>
    <footer>...</footer>
  </div>
</template>

Vue3,我们可以组件包含多个根节点,可以少写一层 !

<template>
  <header>...</header>
  <main>...</main>
  <footer>...</footer>
</template>

组合式API

Vue2 是 选项式API(Option API),一个逻辑会分散在文件不同位置(data、props、computed、watch、生命周期函数等),导致代码的可读性变差,需要上下来回跳转文件位置。
Vue3 组合式API(Composition API)则很好地解决了这个问题,可将同一逻辑的内容写到一起。
除了增强了代码的可读性、内聚性,组合式API 还提供了较为完美的逻辑复用性方案,举个粒子,获取鼠标坐标位置。

// main.vue
<template>
  <span>mouse position {{x}} {{y}}</span>
</template>

<script setup>
import { ref } from 'vue'
import useMousePosition from './useMousePosition'
const {x, y} = useMousePosition()
}
</script>
// useMousePosition.js
import { ref, onMounted, onUnmounted } from 'vue'
function useMousePosition() {
  let x = ref(0)
  let y = ref(0)  
  function update(e) {
    x.value = e.pageX
    y.value = e.pageY
  }
  onMounted(() => {
    window.addEventListener('mousemove', update)
  })
  onUnmounted(() => {
    window.removeEventListener('mousemove', update)
  })
  return {
    x,
    y
  }
}
</script>

解决了 Vue2 Mixin的存在的命名冲突隐患,依赖关系不明确,不同组件间配置化使用不够灵活。

$children(废除)

$children 实例 property 已从 Vue 3.0 中移除,不再支持

vue2.x 语法

在 2.x 中,开发者可以使用 this.$children 访问当前实例的直接子组件:

<template>
  <div>
    <img alt="Vue logo" src="./assets/logo.png">
    <my-button>Change logo</my-button>
  </div>
</template>

<script>
import MyButton from './MyButton'

export default {
  components: {
    MyButton
  },
  mounted() {
    console.log(this.$children) // [VueComponent]
  }
}
</script>
3.x 更新

在 3.x 中,$children property 已被移除,且不再支持。如果你需要访问子组件实例,我们建议使用 $refs。

过滤器(废除)

2.x 语法

在 2.x 中,开发者可以使用过滤器来处理通用文本格式。
例如:

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountBalance | currencyUSD }}</p>
</template>
<script>
  export default {
    props: {
      accountBalance: {
        type: Number,
        required: true
      }
    },
    filters: {
      currencyUSD(value) {
        return '$' + value
      }
    }
  }
</script>

虽然这看起来很方便,但它需要一个自定义语法,打破了大括号内的表达式“只是 JavaScript”的假设,这不仅有学习成本,而且有实现成本。

3.x 更新

在 3.x 中,过滤器已移除,且不再支持。取而代之的是,我们建议用方法调用或计算属性来替换它们。
以上面的案例为例,以下是一种实现方式。

<template>
  <h1>Bank Account Balance</h1>
  <p>{{ accountInUSD }}</p>
</template>

<script>
  export default {
    props: {
      accountBalance: {
        type: Number,
        required: true
      }
    },
    computed: {
      accountInUSD() {
        return '$' + this.accountBalance
      }
    }
  }
</script>

迁移构建开关:

自定义指令(非兼容)

指令的钩子函数已经被重命名,以更好地与组件的生命周期保持一致。
额外地,expression 字符串不再作为 binding 对象的一部分被传入。

2.x 语法

在 Vue 2 中,自定义指令通过使用下列钩子来创建,以对齐元素的生命周期,它们都是可选的:

  • bind - 指令绑定到元素后调用。只调用一次。
  • inserted - 元素插入父 DOM 后调用。
  • update - 当元素更新,但子元素尚未更新时,将调用此钩子。
  • componentUpdated - 一旦组件和子级被更新,就会调用这个钩子。
  • unbind - 一旦指令被移除,就会调用这个钩子。也只调用一次。
    下面是一个例子:
<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
Vue.directive('highlight', {
  bind(el, binding, vnode) {
    el.style.background = binding.value
  }
})

此处,在这个元素的初始设置中,通过给指令传递一个值来绑定样式,该值可以在应用中任意更改。

3.x 语法

然而,在 Vue 3 中,我们为自定义指令创建了一个更具凝聚力的 API。正如你所看到的,它们与我们的组件生命周期方法有很大的不同,即使钩子的目标事件十分相似。我们现在把它们统一起来了:

  • created - 新增!在元素的 attribute 或事件监听器被应用之前调用。
  • bind → beforeMount
  • inserted → mounted beforeUpdate:新增!在元素本身被更新之前调用,与组件的生命周期钩子十分相似。
  • update → 废除!该钩子与 updated 有太多相似之处,因此它是多余的。已改用 updated。
  • componentUpdated → updated
  • beforeUnmount:新增!与组件的生命周期钩子类似,它将在元素被卸载之前调用。
  • unbind -> unmounted

最终的 API 如下:

const MyDirective = {
  created(el, binding, vnode, prevVnode) {}, // 新增
  beforeMount() {},
  mounted() {},
  beforeUpdate() {}, // 新增
  updated() {},
  beforeUnmount() {}, // 新增
  unmounted() {}
}

因此,API 可以这样使用,与前面的示例相同:

<p v-highlight="'yellow'">以亮黄色高亮显示此文本</p>
const app = Vue.createApp({})
app.directive('highlight', {
  beforeMount(el, binding, vnode) {
    el.style.background = binding.value
  }
})

既然现在自定义指令的生命周期钩子与组件本身保持一致,那么它们就更容易被推理和记住了!

边界情况:访问组件实例

通常来说,建议在组件实例中保持所使用的指令的独立性。从自定义指令中访问组件实例,通常意味着该指令本身应该是一个组件。然而,在某些情况下这种用法是有意义的。

在 Vue 2 中,必须通过 vnode 参数访问组件实例:

bind(el, binding, vnode) {
  const vm = vnode.context
}

在 Vue 3 中,实例现在是 binding 参数的一部分:

mounted(el, binding, vnode) {
  const vm = binding.instance
}
v-bind 合并行为(非兼容)

在一个元素上动态绑定 attribute 时,同时使用 v-bind=“object” 语法和独立 attribute 是常见的场景。然而,这就引出了关于合并的优先级的问题。

2.x 语法

在 2.x 中,如果一个元素同时定义了 v-bind=“object” 和一个相同的独立 attribute,那么这个独立 attribute 总是会覆盖 object 中的绑定。

<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="red"></div>
3.x 语法

在 3.x 中,如果一个元素同时定义了 v-bind=“object” 和一个相同的独立 attribute,那么绑定的声明顺序将决定它们如何被合并。换句话说,相对于假设开发者总是希望独立 attribute 覆盖 object 中定义的内容,现在开发者能够对自己所希望的合并行为做更好的控制。

<!-- 模板 -->
<div id="red" v-bind="{ id: 'blue' }"></div>
<!-- 结果 -->
<div id="blue"></div>

<!-- 模板 -->
<div v-bind="{ id: 'blue' }" id="red"></div>
<!-- 结果 -->
<div id="red"></div>
v-if 与 v-for 的优先级对比(非兼容)

Vue.js 中使用最多的两个指令就是 v-if 和 v-for,因此开发者们可能会想要同时使用它们。虽然不建议这样做,但有时确实是必须的,于是我们想提供有关其工作方式的指南。

#2.x 语法
2.x 版本中在一个元素上同时使用 v-if 和 v-for 时,v-for 会优先作用。

#3.x 语法
3.x 版本中 v-if 总是优先于 v-for 生效。

v-model(非兼容)

以下是对变化的总体概述:

  • 非兼容:用于自定义组件时,v-model prop 和事件默认名称已更改: prop:value -> modelValue;
  • 事件:input -> update:modelValue; 非兼容:
  • v-bind 的 .sync 修饰符和组件的 model选项已移除,可在 v-model 上加一个参数代替;
  • 新增:现在可以在同一个组件上使用多个 v-model 绑定;
  • 新增:现在可以自定义v-model 修饰符。
区别

在 Vue 2.0 发布后,开发者使用 v-model 指令时必须使用名为 value 的 prop。如果开发者出于不同的目的需要使用其他的 prop,他们就不得不使用 v-bind.sync。此外,由于v-model 和 value 之间的这种硬编码关系的原因,产生了如何处理原生元素和自定义元素的问题。

在 Vue 2.2 中,我们引入了 model 组件选项,允许组件自定义用于 v-model 的 prop 和事件。但是,这仍然只允许在组件上使用一个 v-model。

在 Vue 3 中,双向数据绑定的 API 已经标准化,以减少开发者在使用 v-model 指令时的混淆,并且更加灵活。

2.x 语法

在 2.x 中,在组件上使用 v-model 相当于绑定 value prop 并触发 input 事件:

<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />

如果想要更改 prop 或事件名称,则需要在 ChildComponent 组件中添加 model 选项:

<!-- ParentComponent.vue -->
<ChildComponent v-model="pageTitle" />
// ChildComponent.vue
export default {
  model: {
    prop: 'title',
    event: 'change'
  },
  props: {
    // 这将允许 `value` 属性用于其他用途
    value: String,
    // 使用 `title` 代替 `value` 作为 model 的 prop
    title: {
      type: String,
      default: 'Default title'
    }
  }
}

所以,在这个例子中 v-model 是以下的简写:

<ChildComponent :title="pageTitle" @change="pageTitle = $event" />
使用 v-bind.sync

在某些情况下,我们可能需要对某一个 prop 进行“双向绑定”(除了前面用 v-model 绑定 prop 的情况)。为此,我们建议使用 update:myPropName 抛出事件。例如,对于在上一个示例中带有 title prop 的 ChildComponent,我们可以通过下面的方式将分配新 value 的意图传达给父级:

this.$emit('update:title', newValue)

然后父组件可以在需要时监听该事件,并更新本地的 data property。例如:

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

为了方便起见,我们可以使用 .sync 修饰符来缩写,如下所示:

<ChildComponent :title.sync="pageTitle" />
3.x 语法

在 3.x 中,自定义组件上的 v-model 相当于传递了 modelValue prop 并接收抛出的 update:modelValue 事件:

<ChildComponent v-model="pageTitle" />

<!-- 是以下的简写: -->

<ChildComponent
  :modelValue="pageTitle"
  @update:modelValue="pageTitle = $event"
/>
v-model 参数

若需要更改 model 的名称,现在我们可以为 v-model 传递一个参数,以作为组件内 model 选项的替代:

<ChildComponent v-model:title="pageTitle" />
<!-- 是以下的简写: -->

<ChildComponent :title="pageTitle" @update:title="pageTitle = $event" />

v-bind anatomy
这也可以作为 .sync 修饰符的替代,而且允许我们在自定义组件上使用多个 v-model。

<ChildComponent v-model:title="pageTitle" v-model:content="pageContent" />
<!-- 是以下的简写: -->
<ChildComponent
  :title="pageTitle"
  @update:title="pageTitle = $event"
  :content="pageContent"
  @update:content="pageContent = $event"
/>

#v-model 修饰符
除了像 .trim 这样的 2.x 硬编码的 v-model 修饰符外,现在 3.x 还支持自定义修饰符:

<ChildComponent v-model.capitalize="pageTitle" />

TypeScript 支持

Vue3 由TS重写,相对于 Vue2 有更好地TypeScript支持。

打包优化

tree-shaking:模块打包webpack、rollup等中的概念。移除 JavaScript
上下文中未引用的代码。主要依赖于import和export语句,用来检测代码模块是否被导出、导入,且被 JavaScript 文件使用。

以nextTick为例子,在 Vue2 中,全局 API 暴露在 Vue 实例上,即使未使用,也无法通过tree-shaking进行消除。

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

Vue3 中针对全局 和内部的API进行了重构,并考虑到tree-shaking的支持。因此,全局 API 现在只能作为ES模块构建的命名导出进行访问。

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

通过这一更改,只要模块绑定器支持tree-shaking,则 Vue 应用程序中未使用的api将从最终的捆绑包中消除,获得最佳文件大小。受此更改影响的全局API有如下。

  • Vue.nextTick
  • Vue.observable (用 Vue.reactive 替换)
  • Vue.version
  • Vue.compile (仅全构建)
  • Vue.set (仅兼容构建)
  • Vue.delete (仅兼容构建)
  • 内部 API 也有诸如 transition、v-model等标签或者指令被命名导出。只有在程序真正使用才会被捆绑打包。

声明周期

在这里插入图片描述

posted @ 2022-03-03 08:42  Cupid05  阅读(171)  评论(0编辑  收藏  举报