一、vue2与vue3区别
1、响应式方面
- vue3中响应式用
ref
和reactive
关于响应式:juejin.cn/post/710762...
2、v-if
和 v-for
一起使用
不推荐一起使用
- 在vue2中,
v-for
优先级大于v-if
- 在vue3中,
v-if
优先级大于v-for
关于为什么不推荐一起使用
3、生命周期
- vue3中的生命周期,名字改为onBeforeMount,加上
on
;销毁由 vue2中的beforeDestory
改为onBeforeUnmout
- 在
script setup
语法糖中,setup
代表了初始化
关于生命周期:
4、vue3中添加了composition API
关于
composition API
:juejin.cn/post/710647...
5、组合式函数与mixin,通过组合式函数复用比mixin好
关于组合式函数:juejin.cn/post/735912...
6、内置组件增加了teleport
、fragment
、suspense
关于新增组件:juejin.cn/post/735830...
7、异步组件写法
- 在vue2中异步组件是返回一个
promise
函数创建,const asyncModal = () => import('./Modal.vue')
- 在vue3中通过
defineAsyncComponent
创建,const asyncModal = defineAsyncComponent(() => import('./Modal.vue'))
8、优化编译过程,重写虚拟DOM,提升渲染功能
关于渲染机制:
9、优化源码体积,更好的tree shaking,减少打包体积
- 在vue3中,移除了不常用的API,例如:inline-template,filter等
- vue3中的API基本添加了Tree shaking,按需引入,如果不需要vue3新增的API,打包的时候是不会打包的
10、vue3使用vite打包,vue2使用webpack打包,两者的区别
关于vite与webpack
二、vue3基础知识
1、响应式数据
1. 响应式数据ref
与reactiv
区别?
-
接受类型:ref可以接受基础类型和对象类型 ;reactive只能接受对象类型
-
使用场景:定义基础类型使用ref;定义对象类型最好使用reactive,避免对象中有value混合
-
响应式数据:ref的响应式数据是RefImpl ,因为Proxy只能代理监听对象类型的数据,监听不了基础类型(Number,String,Boolean,Null,Undefined,BigInt)的数据,所以将基础类型封装为一个对象,将基础类型的读写转换为对对象的value的get和set操作 ,这个时候,读写基础类型的value就是基于对象的get和set内置方法监听数据变化,实现基础类型响应式能力;reactive的响应式数据是Proxy;
2. reactive解构赋值会怎么样?使用什么解构赋值?
-
响应式数据解构赋值后可能会解除响应式联系,当使用解构赋值时,是直接将text复制 一份,没有调用getter,setter,所以不具备响应式
-
toRefs可以将响应式数据中的属性变为响应式:
- toRefs需要传入一个代理对象,否则会报错;
- 然后toRefs内部创建一个新的对象来,遍历传入对象的所有属性,将属性值转为响应式对象,然后挂载到新创建的对象,最后将对象返回;
- 内部会为代理对象的每一个属性创建一个具有value属性的对象,该对象是响应式的,value属性具有getter和setter,所以返回的每个属性都是响应式的
xml
<template>
<textarea v-model="text" placeholder="文本信息" />
<div>文本信息:{{text}}</div>
</template>
<script setup>
import { reactive } from 'vue';
const state = reactive({
text: '今天是2022年01月01日',
});
const { text } = toRefs(state);
</script>
3. shallowReactive,shallowReadonly
注意:慎用浅层响应式作用API,例如shallowReactive,shallowReadonly
- 浅层数据结构应该只用于组件中的根级状态。避免将其嵌套在深层次的响应式对象中,因为它创建的树具有不一致的响应行为,这可能很难理解和调试。
4. 去除响应式toRaw
把响应式关系解除,比如表单中,数据渲染在模板里的表单中,我们可以在表单中更改数据内
容,但是又不希望表单里每次数据变化都触发响应式作用,这个时候就需要用到 toRaw 来解
除掉数据的响应式
如果只是临时修改数据,不想影响视图,就要解除数据的响应式,甚至还要考虑是否需要深拷⻉
2、计算属性computed
-
在get中只读属性,并且只有依赖的值发生改变的时候才会重新计算
-
在set中设置属性,最好避免直接修改计算属性,因为这是个临时存放点,应该改变他所依赖的值
-
get只做计算而没有其他副作用,比如更改DOM,异步请求,不改变其他状态,只做计算和返回值(改变其他状态使用侦听器watch)
-
computed计算的属性值可以是一对多
3、侦听器watch
-
在状态变化时处理一些操作(副作用),比如想更改DOM,异步请求改变状态。实际场景:比如每次响应式状态发生变化时调用函数或者触发请求操作。
-
深度监听时,deep,会监听对象中的所有属性,如果数据结构较大,开销会大,应谨慎使用
-
立即执行:immediate: true;
-
watch侦听的属性值,只能是一对一,第一个参数
1. watch与watchEffect
-
watch第一个参数是侦听值,需要明确表明出来,当第二个参数回调函数中有侦听值时,侦听值改变的同时也会改变回调函数中的,这时候可以简化使用watchEffect;
-
watchEffect第一个参数直接写回调函数,并且可以监听多个侦听值,不需要添加侦听值;不过watchEffect只在同步的时候监听,异步的时候只在第一个await正常工作前被访问到的属性被追踪;
- watch: watch第一个参数是侦听值,确认的;第二个回调函数;只有在侦听值变化时才会更改回调函数中的;不会在产生副作用时追踪侦听值
- watchEffect:只有一个回调函数;追踪回调函数中的响应式属性;有副作用时也会追踪,只能在同步的时候追踪,依赖值不明确
2.watch回调函数操作
-
watch的回调函数是一次性操作很多,批量处理,比如要向侦听的数组中添加一千个数据,是批量处理,不是处理一千次。
-
watch的触发时机是在父组件更新后,当前组件更新前,也就是DOM操作前,所以当前组件访问到的是DOM数据处理前
-
如果想在DOM操作后触发watch,使用
flush: 'post'
arduino
watch(source, callback, {
flush: 'post'
})
watchEffect(callback, {
flush: 'post'
})
或者是使用watchPostEffect
javascript
import { watchPostEffect } from 'vue'
watchPostEffect(() => {
/* 在 Vue 更新后执行 */
})
- 如果想同步触发的话,需要使用flush: 'sync',watchSyncEffect;但是这个最好监听简单的布尔值,因为不能批量操作,不能用于批量操作数组。
- 停止监听:同步的时候,会绑定到当前组件实例上,组件卸载即停止监听;异步的话,需要手动停止监听,最好不用异步,如果需要使用异步数据,使用条件判断语句。
4、v-if,v-show区别?
-
v-if: 进行DOM渲染与销毁 ;只有在为true的时候才进行渲染 ,比如刚开始是false,不渲染,true时才渲染;v-if可以使用在template上 ;有更高的切换开销;
-
v-show:一直渲染 ,进行css的显示隐藏;v-show不能使用在template上 ;有更高的初始渲染开销,频繁操作使用
5、v-for
- v-for: v-for中循环对象,可以写value(值)、key(属性)、index(索引)
- v-for可以使用在template上
1. v-if
和 v-for
一起使用
不推荐一起使用
- 在vue2中,
v-for
优先级大于v-if
- 在vue3中,
v-if
优先级大于v-for
1. 为什么改了优先级?
在vue2中,v-for
优先级大于 v-if
,所以每次需要先进行遍历,遍历后再判断,这样的话我们只渲染出一小部分的元素,也得在每次重渲染的时候遍历整个列表,不论是否发生了变化。不建议这样做的原因就是比较浪费性能 。也可以使用计算属性将v-if
的值和v-for
循环数组合为一个数组遍历。
2. 为什么不支持一起使用?
在vue3中一起使用时,会先进行判断,v-if
的条件将无法访问到 v-for
作用域内定义的变量别名,使用变量名的话会报错。在外新包装一层 <template>
再在其上使用 v-for
或者v-if
,更加通俗易懂
xml
<!--
这会抛出一个错误,因为属性 todo 此时
没有在该实例上定义
-->
<li v-for="todo in todos" v-if="!todo.isComplete">
{{ todo.name }}
</li>
// 改为
<template v-for="todo in todos">
<li v-if="!todo.isComplete">
{{ todo.name }}
</li>
</template>
2. v-for中为什么要使用key?
-
在v-for中会根据就地更改的原则 来进行修改元素,数据项的顺序改变时,Vue 不会随之移动 DOM 元素的顺序,而是就地更新每个元素,确保它们在原本指定的索引位置上渲染。这种模式是高效的,但是只适用渲染元素不依赖子组件或临时DOM状态(例如表单输入值),一般情况都会给key
-
key在diff算法虚拟DOM 中,vnode新旧节点的比较中起到作用,不使用key的话,会移动进行比较值,有key的话,会根据key的顺序进行比较,并且会移除销毁不存在的key
-
还可以根据key值强制替换元素,不是更新元素,这时的key可以用在任何情况下,不一定跟v-for一起使用
3. 为什么不建议index作为Key?key使用Math.random可以吗?
不建议使用index,Math.random作为key。
因为key是唯一的,如果使用index这些,状态不稳定,vue是动态变化的,当数值变化的时候,可能插入到中间末尾等随意位置,这时候index会变化,根据key去找进行比较,这时候Key值也已经变化。
3. v-for与组件一起使用,v-for中循环的内容使用props来接受
6、事件
事件有内联事件处理器(在html上直接写方法内容),方法事件处理器(调用方法):
- 内联处理器调用方法,
@click="say('hello')"
- 内联处理器访问参数,
@click="warn('Form cannot be submitted yet.', $event)"
1. 常见的事件修饰符有哪些?
- 事件有冒泡模式(从下往上触发)和捕获模式(从上往下触发)
- 冒泡效果:子元素和父元素都有绑定事件,这时候会自动向上冒泡,如果要阻止冒泡行为 ,使用stop来阻止
xml
<div>
<a @click.stop="doThis"></a>
</div>
- prevent : 一般用于form表单提交事件,提交后将不再重新加载页面,阻止默认事件,比如说阻止a标签,不让他去触发
ini
<form @submit.prevent="onSubmit"></form>
- self : 只有点击自己的时候才会执行,但是要是在父元素上,子元素都是成对标签,这样点击是不行的,得有自己的内容,比如说文本等
erlang
<div @click.self="doThat">
我是外层div文字
<div>内层div</div>
</div>
- capture : 使用capture是捕获行为,从上到下处理,先处理doThis函数,再处理doThat函数
ini
<div @click.capture="doThis">
<div @click='doThat'></div>
</div>
- once: 只执行一次
- passive : 滚动行为立即发生而不是等待onSroll完成再滚动,主要用于移动端滚屏性能
2. 常见的按键修饰符?
- esc、enter、ctrl、shift、tab、alt、meta、left、right、up、down、space、delete
3. v-on可以实现监听多个方法吗?
v-on监听事件,简写为@。可以监听多个方法,但不推荐这样使用,一般只监听与模板相关的方法。
ini
<div @click="doThis,doThat"></div>
<div @click='doThis;doThat'></div>
这两种写法都可以监听多个方法,事件触发时依次调用这些方法
7、v-model在表单,组件中使用,v-model语法糖?
1. 在表单中使用
就是在input(input,checkbox,radio),textarea,select
2. 在组件上使用v-model:
- 可以实现组件间的双向绑定
- 在3.4之后通过defineModel宏,实现原理与v-model语法糖一样
3. 在vue2中v-model语法糖
4. 在vue3中v-model语法糖
- 在vue3.4之前也是通过把value用作props,把input用作event事件,只不过写法是通过vue3的defineProps,defineEmits 父组件:
ini
<Child v-model:title="bookTitle" />
子组件:
xml
<!-- Child.vue-->
<script setup>
defineProps({
title: {
required: true
}
})
defineEmits(['update:title'])
</script>
<template>
<input
type="text"
:value="title"
@input="$emit('update:title', $event.target.value)"
/>
</template>
- 在3.4开始,使用defineModel来代替value与input 父组件:
ini
<Child v-model:title="bookTitle" />
子组件:
xml
<!-- Child.vue -->
<script setup>
const title = defineModel('title')
</script>
<template>
<input type="text" v-model="title" />
</template>
5. v-model的内置修饰符
- number:自动转换为数字
- trim: 将两边的空格去掉
- lazy: 将input事件改为change事件,change后触发
6. v-model自定义修饰符
- 自定义修饰符,是通过defineModel()或者是defineProps中的set()函数来生成自定义修饰符
父组件:
xml
<script setup>
import { ref } from 'vue'
import MyComponent from './MyComponent.vue'
const myText = ref('')
</script>
<template>
This input capitalizes everything you enter:
<MyComponent v-model.capitalize="myText" />
</template>
子组件:
xml
<script setup>
import { computed } from 'vue'
const [model, modifiers] = defineModel({
set(value) {
// modifiers名字对应,可以修改为其他名;capitalize与父组件名字对应,可修改为其他名
if (modifiers.capitalize) {
return value.charAt(0).toUpperCase() + value.slice(1)
}
return value
}
})
</script>
<template>
<input type="text" v-model="model" />
</template>
8、生命周期
1. vue3中组合式API生命周期:
- 开始渲染组件
- script setup 钩子函数,进行初始化,相当于vue2中的beforeCreate、created
- onBeforeMount : 挂载前,这时候只有响应式完成,未创建DOM,将要首次执行DOM;SSR期间不被调用
- onMounted : 挂载后,虚拟DOM转为实体DOM,能访问到实体DOM ,所有同步的子组件 (不包含异步组件和Suspense组件)挂载完成后父组件挂载 ;SSR期间不被调用;子组件onMounted --> 父组件onMounted
- onBeforeUpdate : DOM修改前,这时候还是旧的数据;SSR期间不被调用
- onUpdated : 修改完成,数据已经更新为新的数据 ,子组件onUpdated-->父组件onUpdated ,vue机制是多个状态更改后会在某一时刻同时批量进行渲染 (考虑到性能问题),所以要单独 在某个状态变更后修改DOM使用nextTick();SSR期间不被调用
- onBeforeUnmount: 卸载前,这时候还能访问到实体DOM中的内容;SSR期间不被调用
- OnUnmounted : 卸载完成,访问不到数据,所有的子组件都会卸载,可以用来处理副作用(计时器,DOM监听,与服务器相关的);SSR期间不被调用
- onErrorCaptured: 捕获错误,可以捕获到错误,一般用于
app.config.errorHandler
全局错误提示;SSR期间不被调用 - onActivated: keep-alive缓存的时候插入到DOM中用,缓存被重新插入,组件挂载时也会被调用;SSR期间不被调用
- onDeactivated: keep-alive卸载时周期;SSR期间不被调用
- onServerPrefretch: 服务器渲染前调用,有时数据存在服务器上,需要调用数据使用,在SSR时期发生
- onRenderTracked: 开发模式下调用,追踪响应式依赖时调用;SSR期间不被调用
- onRenderTriggered: 开发模式下调用,响应式变更触发触发组件渲染时调用;SSR期间不被调用
2. vue3中响应式API生命周期:
- 开始渲染组件
- script setup钩子函数
- beforeCreate: 创建前,初始化选项式钩子,初始化data,method等,双向绑定,响应式已经完成
- created: 判断有无预编译模板,没有等待编译模板;有的话
- beforeMount: 有模板后进行挂载,将vm.$el挂载到el上,将vnode虚拟DOM转化为实体DOM,渲染在界面上
- mounted: 挂载后,此时已经能访问到实体DOM
- 有数据修改时,触发beforeUpdate,此时数据还是未修改前数据;进行虚拟DOM与实体DOM比较,diff算法
- updated: 此时数据已经为修改后数据
- 触发销毁,beforeUnmount,销毁前,此时还能访问到实例内容
- unmounted: 销毁完成,访问不到实例内容
3. vue2中生命周期:
9、模板引用
-
通过ref进行模板引用,ref是在组件挂载后访问,初始化的时候是null,使用ref需要先声明
-
ref声明可以是数组,比如在v-for中使用ref时,声明为数组
-
组件上的ref,可以在父组件中调用子组件的内容,在
<script setup>
中访问是私有的,默认访问不到子组件的内容,子组件需要使用defineExpose
暴露出来;其他情况是可以直接访问