1. 选项式 API(Options API) 变为组合式 API(Composition API)
    Vue 3 借鉴了 React 中的 Hooks 思想,由 Vue 2 的选项式 API 转为组合式 API。所有 API 需要先从 ‘vue’ 中导入才能使用。如:import { reactive, ref, defineProps, toRefs, defineEmits, defineExpose, onUnmounted, watch, onMounted } from 'vue' 这些都是 Vue 3 中的组合式 API , 包括响应式 API(ref , reactive)、生命周期钩子(onMounted……)、依赖注入(provide、inject) 等等。

什么是选项式 API ?
选项式 API(Options API) 就是 Vue 2 中 data、props、computed、methods、components、watch、created 等一些入口配置项,对应类型的 JS 处理只能在对应入口配置项里面来写,如:components 用来组件注册,props 用来接收父组件传参,data 用来数据定义 …… 如下图所示:

什么是组合式 API ?
组合式 API (Composition API) 是一系列 API 的集合,使我们可以使用函数而不是声明选项的方式书写 Vue 组件。
为什么采用组合式 API ?
主要是便于代码阅读、后期维护以及逻辑复用。Vue 2 中当一个组件功能越来越来多,代码量越来越大,整个内容都放在一起显得臃肿,同一个功能模块处理散落在各个位置,导致后期代码难以阅读和维护。而组合式API能将同一个功能模块的代码处理写在一起(data、methods、computed等),也可以单独写在一个脚本文件里(每个脚本对应一个功能模块处理),通过函数导出和导入来在组件里使用。当修改某个功能时直接在对应模块代码里处理即可。方便快捷,便于维护阅读。对比图如下:

  1. Setup 函数(不能用async修饰)
    Setup 函数是作为 Vue 3 中组合式 API 的入口,用来写Vue2 中的props、data、methods、computed、watch等。定义的变量、函数必须要return出来,这样模板中才能使用,很多东西的使用需要在顶部导入对应API(如:import {ref,reactive,computed,onMounted} from ‘vue’)。Setup函数执行时机在 beforeCreate 之前,所以里面访问不到this。 Setup 函数接收两个参数 propscontext, 语法为:setup(props,context){let {attrs,slots,emit} = context} // context有三个属性.
    Setup 函数普通写法可与 Vue2 混合来写,但setup中访问不了 Vue2 选项式 API 中的数据,因为 Setup 函数中访问不到 this 实例。需要注意的是 Vue 2 写法中能获取到 Setup 函数中数据。
<template><imgList /><span> {{ tit }} </span><span> {{ msg }} </span>
</template>
<script>
import ImgList from "./components/ImgList.vue";
import { ref } from "vue"
export default {name: "App",components: {ImgList,},data() {return {tit: '我是 VUE 2 定义的数据'};},methods: {getVue3Data() {console.log('我能获取到 VUE 3 中数据:', this.msg)}},setup() {// 可与 VUE2 选项式 API 混合来写,但无法访问 this 所以获取不到选项式 API 中数据。const msg= ref('Hello world!')// 必须 return 出去,否则模板中读取不到数据return { msg }}
};
</script>

Setup 函数语法糖:<script setup> 简化了 Setup 函数中必须将定义的变量、方法 return 出来,以及导入的组件需要注册的写法,<script setup> 标签中组件导入进来就能使用,不需要再使用 components 注册组件,组件名称就是导入的组件变量名。
< script setup >

<template><imgList /><span> {{ msg }} </span>
</template>
<script setup>
import { ref } from 'vue'
import ImgList from "./components/ImgList.vue";
const msg = ref('Hello world!')
</script>
  1. Vue 3 响应式原理变更
    Vue 2 中数据劫持通过 Object.defineProperty(object, key, description) 实现,这种实现有两个很大的缺陷,一是:只是对对象的单个属性劫持监听,如果 Vue 2 中 data 定义的某个属性又是一个对象,或者对象嵌套对象……,则需要对每个属性作递归遍历处理,一个个地对属性拦截监听;二是:只能添加属性读取( get ) 以及修改( set ) 两种数据操作监听方法,所以 Vue 2 中对对象/数组的属性/元素新增和删除,以及通过索引修改数组元素是监听不到的,所以 Vue 2 中提供了 $set, $delete, $forceUpdate 几个 API 对复杂类型数据操作实现响应式。
    Vue 3 中数据劫持通过 proxy 对整个对象进行代理,解决了 Vue 2 中不能监听对象/数组元素新增/删除操作,以及不能通过索引修改数组元素的问题。proxy 能给代理对象添加多达十几种的拦截方法,里面就包括新增、删除操作的拦截方法。因为 proxy 是对整个对象代理,一次性监听所有属性,不像 defineProperty 需要递归遍历,所以性能更高,缺点就是 proxy 的兼容性没有defineProperty 好,因为 proxy 是 ES6 新增的特性。
new Proxy(source, {// 拦截读取属性值get(source, prop) {return Reflect.get(source, prop)},// 拦截设置属性值或添加新属性set(source, prop, value) {return Reflect.set(source, prop, value)},// 拦截删除属性deleteProperty(source, prop) {return Reflect.deleteProperty(source, prop)}
})

上面代码就是 Vue 3 中采用 Proxy 代理对象进行数据劫持的方式,并且使用了 Reflect 对对象进行增删改读等操作,该操作会有一个返回值true或false代表操作成功或失败。Reflect.set()相较于Object.defineProperty定义属性优势在于,重复定义key不会报错导致代码中断运行。

扩展阅读:

Vue 2 中通过索引修改数组元素其实是可以通过 set 拦截器监听到的,只不过官方放弃了这种拦截方式,因为考虑到实际开发中用到数组,数据量可能成千上万, 这样的话需要再给每个元素添加 setter 拦截,若遇到对象数组,每个对象又有属性是数组的,需要递归遍历处理,这样性能开销过大,和用户体验不成正比,所以官方放弃了这种处理。对于对象而言,一般对象本身属性数量有限,所以对于遍历枚举等方式产生的性能损耗可以忽略不计。 而 Vue 3 用 proxy 代替了 defineProperty 之后就解决了这个问题。Vue 2 中数组也可以通过原生方法 pop(), push(), shift(), unshift(), splice(), sort(), reverse() 等更新数组实现响应式,因为 Vue 2 源码中有通过 defineReactive() 方法对这些数组原生方法进行重写,实现数组响应式更新。

  1. Vue 3 优化了 Diff 算法
    Vue 2 中 diff 算法不管你当前节点有没有更新,都会对所有节点进行比对,而 Vue 3 中由于在模板编译时给静态节点做了静态标记(patchflag),所以 diff 算法在比对过程中,识别是静态节点就会跳过比对,提升了页面渲染速度。
  2. Vue 3 支持摇树优化( tree-shaking )
    在 Vue 3 中,全局和内部 API 都经过了重构,使其能够支持 tree-shaking ,也就是说,如果模块打包工具支持 tree-shaking,那么 Vue 应用中未使用的全局 API 不会被打包进去,减小打包体积。
    官方文档详细介绍
  3. Vue 3 mixins 的变化
    Vue 2 中使用 mixins 抽离公共逻辑,实现代码复用,缺点就是不易阅读、维护,而且 mixins 中生命周期钩子会合并,且先于组件的钩子执行,data、methods 等合并后存在的同名属性会被顶掉。
    mixin:
// mixin.js
export default {components: {},data() {return {msg: 'mixin',}},created() {},methods: {}
}// 组件
<script>
import mixin from './mixin.js'
export default {mixins: [mixin],data() {return {}},created() {console.log(this.msg)},methods: {}
}
</script>

Vue 3 中可以通过自定义 hook ,类型 Vue 2 的 mixins ,将可复用的代码抽离出来,封装成功能函数,将对应 API 导入进来使用。

// common.js 文件
import { ref } from 'vue'
export function run() {let num = ref(0)const add = () => {num.value++}return {num,add}
}
// 组件 .vue 文件
<script setup>
import { run } from './common.js'
let { num, add } = run()
console.log(num.value) // 0
add()
console.log(num.value) // 1
</script>
  1. Vue 3 支持 Fragments 片段
    通俗的讲就是 Vue2 模板中只能有一个根标签,而在 Vue3 模板中可以有多个根节点。

Vue 2:

<!-- 由于不支持多根节点组件,当其被意外地创建时会发出警告。为了修复这个问题,许多组件被包裹在了一个 <div> 中 -->
<template><div><header>...</header><main>...</main><footer>...</footer></div>
</template>

Vue 3:

<!-- 支持多个根节点 -->
<template><header>...</header><main v-bind="$attrs">...</main><footer>...</footer>
</template>

Vue2 中只能有一个根节点,是因为 vdom 是一颗单根树形结构,patch方法在遍历的时候从根节点开始遍历,要求只有一个根节点,组件也会转换为一个 vdom, 故要满足这个要求。
Vue3 中可以有多个根节点,是因为引入了文档碎片( Fragment ) 的概念,它是一个抽象的节点,不实际存在于 DOM 树中,是一种逻辑上存在的东西。若发现组件有多个根节点,就会创建一个 Fragment 节点,将多个根节点作为它的子节点 children, diff 算法在打补丁( patch )比对时候,如果发现是一个 Fragement 节点,则直接遍历 children 创建或更新。

  1. Vue 3 新增 Teleport 传送门标签
    <Teleport> 是一个内置组件,它能将其内包裹的 DOM 元素“传送”到其 DOM 结构外层的指定位置去。
<button @click="open = true">打开对话框</button><Teleport to="body"><div v-if="open" class="modal"><p>我是一个对话框!</p><button @click="open = false">关闭</button></div>
</Teleport>

<Teleport> 上的 to 属性指定传送的目标,它的值可以是一个 CSS 选择器字符串,也可以是一个 DOM 元素对象。上面代码的作用就是告诉 Vue “把以下模板片段传送到 body 标签下”。
使用场景:如:当一个对话框逻辑上属于页面 B 区域内,但其要在 A 区域节点下渲染出来,这时写在 B 区域中的对话框就能使用传送门,将其渲染到指定节点下,就不需要再将对话框移动到 A 区域下来写。
< Teleport >

  1. 生命周期的变化
    Vue 3 中不仅将 Vue 2 中的生命周期钩子改名了(对应阶段所做的事情没有变),还移除了 beforeCreate, created 两个生命周期,整合成了 Setup 函数(注:执行时机在 beforeCreate 之前),新增了两个生命周期(用不上,了解即可)

  2. 自定义指令钩子函数的变化(名称更改)
    Vue 2 中:
    ① bind - 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只是加入进了DOM, 但是渲染没有完成
    ② inserted - 自定义指令所在DOM, 插入到父 DOM 后调用, 渲染已完成(最最重要)
    ③ update - 元素更新, 但子元素尚未更新, 将调用此钩子( 自定义指令所在组件更新时执行, 但是不保证更新完成 ) —> 和自定义所在组件有关
    ④ componentUpdated - 组件和子级更新后执行( 自定义指令所在组件更新完成, 且子组件也完成更新 )—> 和自定义所在组件有关
    ⑤ unbind - 解绑(销毁) ( 自定义指令所在 DOM 销毁时执行 ). 只调用一次。
    Vue 3 中:
    ① created - 自定义指令所在组件, 创建后
    ② beforeMount - 就是Vue2.x中的 bind, 自定义指令绑定到 DOM 后调用. 只调用一次, 注意: 只是加入进了DOM, 但是渲染没有完成
    ③ mounted - 就是Vue2.x中的 inserted, 自定义指令所在DOM, 插入到父 DOM 后调用, 渲染已完成(最最重要)
    ④ beforeUpdate - 自定义指令所在 DOM, 更新之前调用
    ⑤ updated - 就是Vue2.x中的 componentUpdated
    ⑥ beforeUnmount - 销毁前
    ⑦ unmounted - 销毁后
    注:Vue 3 <script setup> 中使用局部自定义指令方式 跳转

  3. 定义组件数据方式不一样
    Vue 2 在 data 中定义:

export default {props: {flag: Boolean},data () {return {name: ''}}
}

Vue 3 在 setup 函数中:

import { ref, reactive } from 'vue'export default {props: {flag: Boolean},setup () {const name = ref('李逍遥')const people = reactive({age: 23})return { name, people }}
}

Vue 3 定义响应式数据,必须借助 ref,reactive 响应式 API,官方推荐 ref 用来定义基本数据类型,reactive 用来定义引用数据类型,虽说如此,但 ref 也可以用来定义引用数据类型,而 reactive 只能用来定义引用数据类型。ref 定义的数据在模板中可以直接使用,但在 setup 里面只能通过 .value 读取其值,reactive 都能直接读取。
注:reactive 定义的引用类型,如 [] 或 {},不能整体去赋值,整体赋值会导致 Vue 监听不到变化,此时需要改变定义策略,用 ref 去定义引用类型。如果坚持用 reactive,需要改变赋值策略,如对象可使用 Object.assign(要修改的对象, 赋值的新对象数据),数组用 push() 方法,数组和对象也能通过 arr[index] = value,obj[key] = value,一个个去设置每个属性值来修改,还有一种方法就是用 reactive 定义的数组或对象,要再包裹一层数组或者对象,来进行整体赋值,详细操作见下面代码。

import { ref, reactive } from 'vue'
setup(props) {const [arr, obj] = [reactive([]), reactive({})]arr = [1,2,3] // 错误赋值方法,监听不到变化arr.push(...[1,2,3]) // 正确赋值方法arr[0] = 1 // 正确赋值方法obj = {key: 1} // 错误赋值方法,监听不到变化Object.assign(obj, {key: 1}) // 正确赋值方法obj.key = 1 // 正确赋值方法// 在外层再包裹一层数组或对象const [arr2, obj2] = [ref({data:[]}), ref({data:{}})]arr2.data = [1,2,3] // 正确赋值方法obj2.data = {key: 1} // 正确赋值方法
}
  1. Vue 3 中子向父通信 emit
    ① 常规:
    父组件:
<template><child :test="test" @changeTest="changeTest" />
</template>
<script>
import { ref } from 'vue';
export default {setup() {const test = ref('666')// 子调用父中方法修改 props 传承const changeTest = v => {test.value = v}return {test, changeTest}}
}
</script>

子组件 child:

<script>
import { ref, watch, watchEffect } from 'vue';
export default {props: {test:{type:String,default: 'test'}},setup(props, { emit }) {const test = ref('123')// 子向父传参const changeData = v => {emit('changeTest', v)}// 监听器使用案例// newV:新值,oldV:旧值watch(()=> props.test,(newV, oldV) => {console.log('test值以改变')})// 复杂数据类型可开启深度监听watch(()=> props.test,(newV, oldV) => {console.log('test值以改变')}, { deep: true })watchEffect(() => {console.log(props.test)})return {test, changeData}}
}
</script>

② < script setup > 子组件 child 中:

<script setup>
import { ref, toRefs, defineProps, defineEmits } from 'vue';
const props = defineProps({test: {type: String,default: ''}
})
// props 不是常规对象,是 reactive 类型,不能直接结构里面属性,否则不是响应式,
// 需要通过 toRefs 进行结构,toRefs 会将对象内的所有属性封装成 refImpl 类型
const { test } = toRefs(props) // test 是 refImpl 类型
console.log(test.value)
const $emit = defineEmits(['changeTest'])
const changeData = v => { $emit('changeTest', v) }
</script>

父子通信高级写法,后续单独通过一篇文章来讲解。

  1. Vue 3 新增 watchEffect 监听器。
    watch: 需要给出具体要侦听的数据,惰性的,只有侦听的数据改变才会执行回调,组件初次加载时不会执行,可以拿到新值、旧值。(也有 immediate, deep 俩参数,监听复杂数据要开启深监听。用法:watch( () => state, (newValue, oldValue) => { // newValue === oldValue }, { deep: true, immediate: true } ), immediate 为 true 时, 效果和 watchEffect 等价)
    watchEffect: 不需要给出侦听数据,自动感知依赖,即回调里访问哪些响应式数据,其变化自动触发。回调会在第一次加载时立即执行一次,之后响应式数据变化时也会触发。获取不到旧值。

watch, watchEffect 使用示例见上面序号 9. 的代码示例

  1. Vue3中移除了 s e t 、 set、 set、forceUpdate、.sync等api和修饰符。

  2. 插槽写法不同
    在 Vue2.x 中具名插槽和作用域插槽分别使用 slot 和 slot-scope 来实现,Vue2 中 v-slot="插槽名" slot-scope="变量名" 单独使用。
    在 Vue3.x 中将 slot 和 slot-scope进行了合并统一使用:

  3. CSS 中可以使用 v-bind()
    单文件组件的 <style> 标签支持使用 v-bind CSS 函数将 CSS 的值链接到动态的组件状态:

<template><div class="text">hello</div>
</template><script>
export default {data() {return {color: 'red'}}
}
</script><style>
.text {color: v-bind(color);
}
</style>

setup 语法糖书写方式:

<script setup>
const theme = {color: 'red'
}
</script><template><p>hello</p>
</template><style scoped>
p {color: v-bind('theme.color');
}
</style>

实际的值会被编译成哈希化的 CSS 自定义属性,因此 CSS 本身仍然是静态的。自定义属性会通过内联样式的方式应用到组件的根元素上,并且在源值变更的时候响应式地更新。

Vue 3 与 Vue 2 的区别,相比 Vue 2 有哪些升级、改变之处相关推荐

  1. vue框架和uniapp框架区别,前端vue和uniapp哪个好用

    uniapp和vue有什么区别? vue和uni-app的区别如下:1.uni-app可以通过打包实现一套代码多端运行,而vue不行.2.uni-app有自动的框架预载,加载页面的速度更快,vue没有 ...

  2. vue方法调用失败后多次调用_浅析Vue中 computed / watch / methods的区别

    思考:请说下Vue中computed 和 watch 的区别( 面试题 ) 构造选项 computed / watch / methods computed ● computed 起初构想 在Vue的 ...

  3. vue设置cookie的domain无效_【Vue.js入门到实战教程】16Tailwind 与 Bootstrap 的区别和使用入门...

    来源 | https://xueyuanjun.com/post/22065我们知道,从 Laravel 8 开始,自带前端脚手架代码默认兼容 Tailwind CSS 框架,取代了之前的 Boots ...

  4. [vue] 组件和插件有什么区别?

    [vue] 组件和插件有什么区别? 组件 (Component) 是用来构成你的 App 的业务模块,它的目标是 App.vue.插件 (Plugin) 是用来增强你的技术栈的功能模块,它的目标是 V ...

  5. 七、Vue cli详解学习笔记——什么是Vue cli ,Vue cli的使用(安装,拉取2.x模板,初始化项目),Vue cli2详解,Runtime-Compiler和Runtime-only区别

    一.什么是Vue CLI 如果你只是简单写几个Vue的Demo程序, 那么你不需要Vue CLI. 如果你在开发大型项目, 那么你需要, 并且必然需要使用Vue CLI 使用Vue.js开发大型应用时 ...

  6. 微信小程序和vue双向绑定哪里不一样_浅析Vue 和微信小程序的区别、比较

    写了vue项目和小程序,发现二者有许多相同之处,在此想总结一下二者的共同点和区别. 一.生命周期 先贴两张图: vue生命周期 小程序生命周期 相比之下,小程序的钩子函数要简单得多. vue的钩子函数 ...

  7. vue计算属性与监听器的区别

    vue计算属性与监听器的区别 计算属性(computed) 监听器(watch) 监听data中没有的值 监听data中有的值 使用时触发 使用不会触发 值有变化就触发 值有变化就触发 没有settt ...

  8. Vue基础——VueJS是什么、Vue的优缺点、vue2和vue3的模板区别、MVVM数据双向绑定、Vue的安装和使用、Vue模板语法-文本渲染、常用的vue的指令

    目录 一.VueJS是什么? 二.Vue的优缺点 三.MVVM 数据双向绑定 四.Vue的安装和使用 五.Vue模板语法-文本渲染 六.常用的vue的指令 一.VueJS是什么? 它是一个轻量级MVV ...

  9. 创建VUE项目,vue-cli2.0版本和3.0版本的区别,将vue2.0项目升级为vue3.0项目

    创建VUE项目,vue-cli2.0版本和3.0版本的区别,将vue2.0项目升级为vue3.0项目 使用vue-cli2.0版本创建vue项目 创建前的准备 开始创建 创建过程 项目正常创建 使用v ...

最新文章

  1. Windows Server 2008英文正式版安装体验
  2. android floatingactionbutton样式,Android 之 FloatingActionButton
  3. 基本语法及基本概念概述(标识符、访问修饰符、变量、数组、枚举、注释、空行、继承、接口、(对象、类、方法、实例变量)、关键字表)
  4. Chunky Monkey-freecodecamp算法题目
  5. python分布式进程(windows下)
  6. Spring自动扫描组件
  7. 只会使用 WaitGroup?你应该学习下 ErrGroup!
  8. 更优雅的在 Xunit 中使用依赖注入
  9. 布尔类型(boolean/Boolean)自动生成的get方法需要注意的小细节
  10. linux 运行scrapy,python 文件 运行 scrapy
  11. SpringBoot整合Redis 主从复制_02
  12. python文字处理dummy_python中multiprocessing、multiprocessing.dummy和threading用法笔记
  13. 内核移植(4)移植yaffs文件系统
  14. e480 黑苹果_记一次黑苹果PC装机全过程
  15. 根据Excel记录生成Mysql和Hive建表语句
  16. 小米手机已连接但无法访问互联网解决办法
  17. [Codeforces266E]More Queries to Array...——线段树
  18. 九度_题目1361:翻转单词顺序
  19. Manifest.json文档说明
  20. gdb使用watch命令设置数据断点

热门文章

  1. 泛生子Q3财报解析:癌症基因检测行业已呈现“燎原之势”
  2. 如何使用Ghost制作系统镜像文件
  3. 【老王的脑科学谬论】CSDN问答区老王谬论悬赏辩论赛二番战(再次求锤失败4月6日已结题)
  4. 2.4G无线音频传输方案
  5. 关闭Google安全搜索,实现无限制搜索
  6. 钢板弹簧的设计(说明书+9张CAD图纸)
  7. 【Appium+Python】进行手机操作的方法+使用手机物理键
  8. 网站运营方案SEO【运营】
  9. 硬件基础——驱动电路
  10. 开放式激光振镜+运动控制器(四):PSO位置同步输出在激光振镜加工中的应用