一、vue2和vue3的区别
1、数据绑定原理不同
vue2:数据绑定是利用ES5的一个API:**Object.definePropert()**对数据进行劫持,结合发布订阅模式的方式来实现的。
vue3:使用了ES6的Proxy API对数据代理。相比vue2.x,使用proxy的优势如下:
- defineProperty只能监听某个属性,不能对全对象监听;
- 可以省去for in,闭包等内容来提升效率(直接绑定整个对象即可);
- 可以监听数组,不用再去单独的对数组做特异性操作vue3.x可以检测到数组内部数据的变化。
2、是否支持碎片
vue2:不支持碎片。
vue3:支持碎片(Fragments),或者说它可以拥有多个根节点。
3、API类型不同
vue2:使用选项式( 选项型**)**api,选项型api在代码里分割了不同的属性:data,computed,methods等。
vue3:使用组合式api,新的合成型api能让我们使用方法来分割,相比于旧的api使用属性来分组,这样代码会更加简便和整洁。
4、定义数据变量和方法不同
vue2:是把数据放入data中,在vue2中定义数据变量是data(){},创建的方法要在methods:{}中。
vue3:就需要使用一个新的setup()方法,此方法在组件初始化构造的时候触发。使用以下三个步骤来建立反应性数据:
- 从vue引入reactive;
- 使用reactive() 方法来声明数据为响应性数据;
- 使用setup()方法来返回我们的响应性数据,从而template可以获取这些响应性数据。
5、生命周期钩子函数不同
vue2:生命周期:
- beforeCreate 组件创建之前
- created 组件创建之后
- beforeMount 组价挂载到页面之前执行
- mounted 组件挂载到页面之后执行
- beforeUpdate 组件更新之前
- updated 组件更新之后
vue3:生命周期:
- setup 开始创建组件前
- onBeforeMount 组价挂载到页面之前执行
- onMounted 组件挂载到页面之后执行
- onBeforeUpdate 组件更新之前
onUpdated 组件更新之后而且vue3.x 生命周期在调用前需要先进行引入。除了这些钩子函数外,其中vue3.x它还增加了onRenderTracked 和onRenderTriggered函数。
6、父子传参不同
vue2:父传子,用props,子传父用事件 Emitting Events。在vue2中,会调用this$emit然后传入事件名和对象。
vue3:父传子,用props,子传父用事件 Emitting Events。在vue3中的setup()中的第二个参数content对象中就有emit,那么我们只要在setup()接收第二个参数中使用分解对象法取出emit就可以在setup方法中随意使用了。
7、指令与插槽不同
vue2:中使用slot可以直接使用slot;v-for与v-if在vue2中优先级高的是v-for指令,而且不建议一起使用。
vue3:中必须使用v-slot的形式;vue3中v-for与v-if,只会把当前v-if当做v-for中的一个判断语句,不会相互冲突;vue3中移除keyCode作为v-on的修饰符,当然也不支持config.keyCodes;vue3中移除v-on.native修饰符;vue3中移除过滤器filter。
8、main.js文件不同
vue2:中我们可以使用pototype(原型)的形式去进行操作,引入的是构造函数。
vue3:中需要使用结构的形式进行操作,引入的是工厂函数;vue3中app组件中可以没有根标签。
9、Vue3带来了什么
更快的渲染速度:Vue3使用了Proxy代理对象,可以更快地跟踪数据变化,从而提高渲染速度。
更小的体积:Vue3的体积比Vue2更小,同时也支持按需加载,减少了页面加载时间。
更好的TypeScript支持:Vue3对TypeScript的支持更加完善,可以更好地进行类型检查和代码提示。
更好的组件封装:Vue3引入了Composition API,可以更好地封装组件逻辑,使得组件更加可复用和易维护。
更好的响应式系统:Vue3的响应式系统进行了重构,可以更好地处理嵌套对象和数组的变化,同时也提供了更多的API来处理响应式数据。
总之,Vue3带来了更好的性能、更小的体积、更好的TypeScript支持、更好的组件封装和更好的响应式系统,使得开发者可以更加高效地开发Vue应用。
二、vue2和vue3响应式原理(Object.defineProperty和Proxy的区别)
1、 vue2的响应式原理
- vue2的响应式实现主要是利用了Object.defineProperty的方法里面的setter 与getter方法,来完成数据劫持
- 使用观察者模式来实现响应式的变化。
- 具体做法: 在组件初始化时会给data的每一个属性注册getter和setter,然后再new 一个自己的Watcher对象,此时watcher会立即调用组件的render函数去生成虚拟DOM。在调用render的时候,就会需要用到data的属性值,此时会触发getter函数,将当前的Watcher函数注册进sub里(订阅)。当data属性发生改变之后,就会遍历sub里所有的watcher对象,通知它们去重新渲染组件(发布)。
- Vue2的响应式原理存在一些缺陷,例如无法监听数组的变化,需要通过特殊的方法来实现
2、vue3的响应式原理:
1)、 vue3用Proxy代替了Object.defineProperty。
- Proxy 可以直接监听对象而非属性,可以直接监听数组的变化;
- Proxy 有多达13 种拦截方法,不限于 apply、ownKeys、deleteProperty、has 等等是 Object.defineProperty 不具备的。
- Proxy 返回的是一个新对象,我们可以只操作新的对象达到目的,而 Object.defineProperty 只能遍历对象属性直接修改;
- Proxy也解决了数组监听的问题,不用再去单独的对数组做特异性操作vue3.x可以检测到数组内部数据的变化。也解决了用下标修改数组元素不是响应式的问题。
- Proxy的性能比Object.definePropery高10倍。
- Vue3使用了WeakMap来存储依赖关系,避免了Vue2中Watcher的内存泄漏问题。
三、组合式api和选项式api的区别
1、组织代码的方式:
选项式 api是按照 **选项(配置项)**的思路来分隔组织代码的,例如 data、methods 和 mounted。这些选项所定义的属性都会暴露在函数内部的 this 上,指向当前的组件实例。
组合式 api是按照 业务逻辑的方式分隔组织代码的。组合式api中使用函数的方式代替选项式api的配置项。让代码组织更加灵活,在任何需要的地方调用对应的函数。比如:在需要定义响应式数据的地方,直接调用ref或者reactive函数既可以(不需要像选项式api那样,非得写在data配置项里)。
2、代码的复用:
选项式api使用mixin来完成,mixin里也是配置项。容易发生命名冲突且关系不清。不够灵活(如果:只需要引入某个函数,就需要把整个混入对象都引入,颗粒度不够小)。
组合式api使用组合式函数的方式,让代码更加灵活,复用性更好,组件里面的逻辑可以进行模块化。也不存在this的问题。
3、对TS的支持:
组合式api 对TS的支持更好。
4、单元测试:
组合式api由于使用的是函数的方式,所以,更方便做单元测试。
5、使用场景:
选项式api适用于小型和中型项目。否则,代码逻辑复杂后,导致组件关系复杂,单个组件内部的代码太多。
组合式api适用于大型项目,当然小中项目也是没有问题。
四、vue3新增的响应式相关的函数
ref,reactive,readonly,computed,watch,watchEffect
1、ref的理解
Vue2版本:
在Vue2该版本中,ref的作用总结来说无非就是两种:1.获取页面DOM元素。2.父子组件之间通信,获取子组件的对象。下面举个例子来说明其用法:
子组件对应代码
<template>
<view>
<input :focus="isFocus" type="text" placeholder="请输入内容" />
</view>
</template>
<script>
export default {
name:"base-input",
data() {
return {
"isFocus":false
};
},
methods:{
focus(){
this.isFocus = true
}
}
}
</script>
父组件对应代码
<template>
<view>
<base-input ref="usernameInput"></base-input>
<button type="default" @click="getFocus">获取焦点</button>
</view>
</template>
<script>
export default {
methods:{
getFocus(){
//通过组件定义的ref调用focus方法
this.$refs.usernameInput.focus(),
console.log(this.$refs.usernameInput.isFocus)
}
}
}
</script>
访问子组件实例或子元素,通过 ref 为子组件赋予一个 ID 引用,在vue的js中可通**this.$refs.XXX
**来获取到组件对象
Vue3版本
响应式数据的优点
- 数据一旦发生变化,系统能够立即响应,无需等待用户的显式请求。这种机制显著提高了数据处理的实时性和效率,尤其是在处理大量数据时更为明显。通过减少不必要的请求和等待时间,响应式数据系统能够更快地处理数据,从而优化整体性能。
- 用户在使用响应式数据驱动的应用时,能够即时获得数据变化的反馈,无需长时间等待系统响应。这种即时性提升了用户的满意度和体验。
- 响应式数据系统通常只需要存储最新的数据,无需保留大量历史数据,从而节约了存储空间。
- 响应式数据系统具有高度的自动化和即时响应特性,因此降低了系统维护的复杂性和成本。
- 响应式数据系统能够确保数据在多个组件或服务之间保持一致性,提高数据的可靠性。
- 在前端开发中,响应式数据能够自动驱动视图的更新,当数据发生变化时,无需手动操作DOM即可实现视图的同步更新,简化了开发流程并提高了开发效率。
所以,为了更好的完成客户端与服务端交互,将运行时实时更新的客户端需求与服务端响应做到及时反馈与连接是优化网页的刚需,那么将特定数据转变成响应式数据则成为必不可少的要求。
如何将数据变成响应式数据?
在Vue3版本中,系统为我们编写提供了简便方法,那就是通过ref包裹数据,将源数据便捷的转变为响应式数据,下面通过简单例子来诠释:
<template>
<div>
<p>{{ count }}</p>
<button @click="increment">Increment</button>
</div>
</template>
<script>
import { ref } from 'vue'
export default {
setup() {
// 使用 ref 创建一个响应式的数据对象
const count = ref(0)
// 定义一个方法来修改这个响应式数据
function increment() {
count.value++
}
// 将响应式数据和修改数据的方法返回,使其能在模板中被访问
return {
count,
increment
}
}
}
</script>
在这个实例中,首先通过 ref 定义一个名为 count 的响应式数据对象,并将其初始值设置为 0。然后,定义了一个函数名 increment的 方法,用于在点击按钮时递增 count 的值。最后,通过返回一个对象,将 count 和 increment 方法暴露给模板,使其能够在模板中被访问和使用。
需要注意的一个地方,在vue3的ref 使用中,将数据转换的方式就是用ref 将想要转换的数据通过括号包裹,在<template>中调用也和平常无异,但若想要将数据进行更改,那么在<script>中必须添加后缀 .value,才能实现对其精准更正。一般的,通过定义一个触发事件,来进行用户体验良好,交互性更强的动态改变。
注意:vue2和vue3中的ref用法截然不同,并且两者的作用不同。vue2中ref主要实现信息的传递,而vue3中主要实现数据的响应式处理。
2、reactive的理解
是一个用于创建响应式数据的函数,它接收一个对象作为参数,并返回一个代理对象,该代理对象可以跟踪对象的变化,并在对象发生更改时通知相关的依赖项。使用Reactive方法,可以将普通的JavaScript对象转换为响应式对象,从而使相关的组件在对象发生更改时自动重新渲染。这是通过Vue.js的响应式系统实现的,该系统能够跟踪属性的变化,并在发生更改时触发重新渲染
功能: 接受一个对象,返回一个对象的响应式代理(proxy)。返回的对象以及其中嵌套的对象都会通过 ES Proxy 包裹,因此不等于源对象,建议只使用响应式代理,避免使用原始对象。
响应式转换是"深层"的:它会影响到所有嵌套的属性。一个响应式对象也将深层地解包任何 ref 属性,同时保持响应性。
总的来说,Reactive方法是Vue.js 3中用于创建响应式数据的重要工具,它使得开发者能够更方便地处理复杂的数据对象,并在数据变化时自动更新UI,从而提高应用程序的响应性和用户体验。
注意点:当访问到某个响应式数组或 Map 这样的原生集合类型中的 ref 元素时,不会执行 ref 的解包。
3、ref和reactive的区别
ref和reactive的主要区别在于它们处理的数据类型、访问方式以及更新触发机制。
数据类型:
ref
:适用于基本数据类型(如字符串、数字、布尔值等),它返回一个包含value
属性的对象,通过修改value
属性的值来触发组件更新。reactive
:适用于复杂对象或数组,它返回一个响应式的Proxy对象,通过修改该对象的属性值来触发组件更新。
访问方式:
ref
:需要通过.value
属性来访问和修改值,例如count.value
。在模板中,如果ref
作为顶层属性被访问,则会自动解包,不需要使用.value
。reactive
:可以直接访问和修改对象或数组的属性,例如state.count
,无需使用.value
属性。
更新触发机制:
ref
:通过修改.value
的值或直接赋值给.value
来触发更新。reactive
:通过直接修改对象或数组的属性来触发更新。
总结来说,ref
更适合处理简单的响应式数据,而reactive
更适合处理复杂的对象或数组,提供更灵活的响应式更新机制。
五、其他
1、toRef和toRefs
- 相同点
toRef 和 toRefs 可以用来复制 reactive 里面的属性然后转成 ref,而且它既保留了响应式,也保留了引用,也就是你从 reactive 复制过来的属性进行修改后,除了视图会更新,原有 ractive 里面对应的值也会跟着更新,如果你知道 浅拷贝 的话那么这个引用就很好理解了,它复制的其实就是引用 + 响应式 。
- 不同点:
toRef: 复制 reactive 里的单个属性并转成 ref
toRefs: 复制 reactive 里的所有属性并转成 ref
2、shallowReactive 与 shallowRef
- hallowRef:只处理基本数据类型的响应式
- shallowReactive:只处理对象最外层属性的响应式(浅响应式)
- 浅层作用的响应式数据处理:只处理第一层对象的数据,再往下嵌套的数据,操作数据是不起作用的
- shallowReative与shallowRef在某些特殊的应用场景下,是可以提升性能的,前者针对对象,用于浅层作用的响应式数据处理,而后者只处理基本数据类型的响应式,不进行对象的响应式处理。
3、readonly 与 shallowReadonly【扩展】
readonly与shallowReadonly都是让响应式数据只具备读的能力,后者是浅层次的只读,也就是只对数据对象第一层起作用,深层次的嵌套,当时用shallowReadonl()处理时,深层次数据支持被修改
- readonly: 让一个响应式数据变为只读的 (深只读),让一个响应式数据变为只读的,接收一个响应式数据,经过readonly加工处理一下,那么新赋值的数据都不允许修改
- 接受一个对象 (不论是响应式还是普通的) 或是一个 ref,返回一个原值的只读代理
- shallowReadonly: 让一个响应式数据变为只读的 (浅只读),接收一个响应式数据,经过shallowreadonly的处理,变成一个只读的,只考虑对象的第一层数据,不可以修改,但是第一层嵌套里的深层数据却支持修改
- 让一个响应式数据变为只读能力(浅只读)
4、toRaw与markRaw转换为普通数据和标记属性非响应式【扩展】
toRaw,将响应式对象(由 reactive定义的响应式)转换为普通对象,然后赋值给新的变量(不影响原来的对象)
markRaw,标记一个对象,使其不能成为一个响应式对象。
toRaw 作用:将一个由 reactive 生成的响应式对象转为普通对象 使用场景:
- 用于读取响应式对象对应的普通对象
- 对这个普通对象的所有操作,不会引起页面更新。
markRaw: 作用:标记一个对象,使其永远不会再成为响应式对象 应用场景: 1、有些值不应被设置为响应式的,例如复杂的第三方类库等 2、当渲染具有不可变数据源的大列表时,跳过响应式转换可以提高性能
5、自定义hooks(组合式函数)的规则和作用:
在Vue 3中,可以使用组合式 API(Composition API)创建自定义的hooks(也被称为自定义钩子函数)。这些函数通常会包含一些逻辑,可以在多个组件之间复用。
自定义hooks的规则:
-
函数名以
use
开头,表示这是一个hooks。 -
内部使用Vue 3的响应式API,通常是
ref
,reactive
,computed
,watch
等。 -
应该返回响应式的数据或方法,以便在组件中使用。
-
不应该直接操作DOM,而是返回操作的方法供组件使用。
-
应该避免在hooks内部进行副作用操作,副作用操作应当放在使用hooks的组件内部或其他hooks中。
hooks调用:
- hooks必须在setup函数的根级调用,即:在setup函数执行时,hooks就要被调用
- hooks函数可以在其它hooks函数里调用。
作用:
以函数形式抽离一些可复用的方法像钩子一样挂着,放在外部的js文件里。随时可以引入和调用,实现高内聚低耦合的目标;引用时将响应式变量或者方法显式解构暴露出来如:const {nameRef,Fn} = useXX()
下面是一个简单的自定义hook的例子,它创建了一个可以跟踪的计数器:
// useCounter.js
import { ref } from 'vue';
export function useCounter(initialValue = 0) {
const count = ref(initialValue);
function increment() {
count.value++;
}
function decrement() {
count.value--;
}
return { count, increment, decrement };
}
然后在组件中使用这个hook:
<template>
<div>
<p>Count: {{ count }}</p>
<button @click="increment">Increment</button>
<button @click="decrement">Decrement</button>
</div>
</template>
<script>
import { useCounter } from './useCounter';
export default {
setup() {
const { count, increment, decrement } = useCounter(0);
return { count, increment, decrement };
}
};
</script>
6、watch与watchEffect
watch和watchEffect都是vue3中的监听器,但是在写法和使用上是有区别的,
1)、监听源:
watch函数的监听源会明确指定,是watch函数的第一个参数。
watchEffect函数的监听源是回调函数中使用的所有的响应式数据。自动收集依赖。
2)、懒侦听:
watch默认是懒侦听,如果要立即执行,则设置第三个参数的配置项immediate 为true。
watchEffect不是懒侦听,组件一旦执行,那么,回调函数立即会调用
3)、深度侦听:
watch默认不是深度侦听,如果需要,则设置第三个参数的配置项deep为true。
watchEffect默认是深度侦听的。
4)、是否能够拿到旧值:
watch可以。
watchEffect不可以。
7、provide与inject
provide 和 inject 是一对新的API,用于在父组件中提供数据,然后在子组件中注入数据。
-
provide:是一个对象,或者是一个返回对象的函数。里面呢就包含要给子孙后代的东西,也就是属性和属性值。如果希望提供的数据是响应式的,那么,值就需要是响应式的数据。
-
inject:一个字符串数组,或者是一个对象。属性值可以是一个对象,包含from和default默认值。
//在父组件中,使用provide提供数据:
//name:定义提供 property的 name。
//value :property的值。
setup(){
provide('info',"值")
}
//在子组件中,使用inject注入数据
//name:接收 provide提供的属性名。
//default:设置默认值,可以不写,是可选参数。
setup(){
const info = inject("info")
inject('info',"设置默认值")
return {
info
}
}
8、vue3中定义全局属性
- 通过config.globalProperties
- 通过provide注入:在应用实例上设置一个可以被注入到应用范围内所有组件中的值。当组件要使用应用提供的 provide 值时,必须用 inject 来接收。
- 在main.js中全局引入,然后在组件中获取
9、响应式数据的判断isRef、isReactive、isReadonly、isProxy
- isRef:判断一个值是否为一个 ref 对象
- isReactive:判断一个对象是否是由 reactive创建的响应式代理
- isReadonly:判断一个对象是否是由 readonly 创建的只读代理
- isProxy:判断一个对象是否是由 reactive 或 readonly 创建的代理
六、setup配置(setup函数的参数)
Vue 3中的 setup 函数接收两个参数,分别是 props 和 context。
1、props:父组件传来的属性值
包含:组件外部传递过来。切组件内部声明接收了的属性。需要注意的是,Vue3中的 props 是只读的,即在setup 函数中不能修改 props 的值。如果需要修改传递过来的数据,可以使用响应式对象或ref。
2、context:上下文对象。
1)、attrs:值为对象,包含组件外部传递过来,但没有在props配置中声明的属性。相当于this.$attrs
2)、slots:收到的插槽内容,相当于this.$slots
3)、emit:分发自定义事件的函数,相当于this.$emit
注意:
1)、这个钩子会在created之前执行
2)、内部不存在this
3)、如果返回值是一个对象,那么这个对象中的键值对会被合并到created钩子的this中,而在视图上也能访问相应的数据值