vue2和vue3的不同
- main.js中,不再引入Vue构造函数
import Vue from 'vue'
,而是引入createApp工厂函数import { createApp } from 'vue'
,意为创建一个应用对象。 ⟶ \longrightarrow ⟶ 构造函数必须通过 new 创建实例,工厂函数不需要。 createApp(App).mount('#app')
代替了Vue2中的new Vue( {render: h=>h(App)} ).$mount('#app')
:创建一个应用实例对象,该对象类似于Vue2中的vm,但没有那么多的属性,比vm更“轻”;然后挂载根组件。- Vue3的组件中,
< template >
里面可以没有根标签。
一. 组合式API
1.1. setup()
组件中用到的数据、方法等,都要配置在setup
中。
返回值:
对象:对象中的属性、方法在木板中可以直接使用,不用加this
;
渲染函数:可以自定义渲染内容import {h} from 'vue'
引入渲染函数,setup(){}
需要return () => h('渲染元素', '渲染内容')
,返回的箭头函数执行后会返回h
的执行结果。
如果在一个.vue
文件中同时配置了vue2
的data、methods
等选项和vue3
中的setup
选项:
vue2
的方法可以访问vue3
的属性,可以调用vue3
的方法;
vue3
的方法不能访问vue2
的属性,不能调用vue2
的方法;
当vue3
和vue2
配置的属性或方法名字冲突的时候,以vue3
为准;
⟶
\longrightarrow
⟶ 不要混着用
如果一个组件是静态引入的,那么setup()
不能写成async
函数,因为返回值就不再是return
的对象,而是一个promise
,模板看不到return
对象中的属性;
如果组件是动态引入的并用了Suspense
,那么就可以写成async
了,并返回一个promise
对象。
一些细节
setup()
在beforeCreate()
钩子之前执行,并且setup()
的this
是undefined
;
setup()
默认能接收的第一个参数是props
,第二个参数是context
;
要先在组件里接收了props
才能正常用,不然会抛出警告;props
接收到的所有数据会被自动整理到一个Proxy
对象中,成为响应式的数据;
context
是一个普普通通的对象,关注里面的attrs、emit、slots
;attrs
相当于Vue2
中的this.$attrs
,接收子组件的props
不要的、被父组件传进来的数据;emit
相当于Vue2
中的this.$emit
,但是在使用前要先在emits
配置项中接收父组件传来的自定义事件,接收方法和props
一样;slots
里是收到的插槽的内容,相当于Vue2
中的this.$slots
。
1.2. ref()
引入ref:import {ref} from 'vue'
在setup中定义属性时:
对于基本数据类型:使用let name = ref("yyt")
而不是let name = "yyt"
,才能让属性成为响应式的;此时name
成为了一个RefImpl
对象,name.value
才等于"yyt"
,所以修改属性值时要用name.value
;
通过Object.defineProperty()
的getter 和 setter 实现数据劫持,get和set方法被放在了RefImpl
对象的原型中。
对于对象类型:使用let names = ref({ name:"yyt", friend: "kirlant"} )
,用Vue3中的reactive
方法将传入的对象转换成了ES6的Proxy
代理对象,赋给names
。
用js操作数据时需要.value
,读取数据时模板会自己解析,不用.value
;
RefImpl:reference implement 引用实现,
1.3. reactive()
定义一个对象或者数组类型的响应式数据,返回一个Proxy
类型的对象;
数组可以直接通过索引修改了诶;
基于ES6的Proxy
实现响应式(数据劫持),通过reflect
操作源对象内部的数据;
实现了深层次的响应式,不管对象嵌套了多少层,其中的每一个数据都被变成了响应式的;
操作与读取都不需要.value
;
Vue3的响应式原理
通过Proxy
拦截对象中任意属性的变化,包括属性值的读写、属性的添加删除等;通过Reflect对被代理对象的属性进行操作。
1.4. 计算属性与监视
1.4.1. computed
computed
成为了组合式API,需要import { computed } from 'vue'
,然后就可以写在setup()
里面了;
如果计算属性是只读的,可以let calcuData = computed(回调函数)
,回调函数需要return
最终的结果给calcuData
;
如果计算属性可以被读和写,computed()
需要接受一个对象,对象里是get()
和set()
,get()
返回根据依赖的值计算出的计算属性的值,set(value)
接收被修改的值后进行一些操作。
1.4.2. watch
watch
成为了组合式API,需要import { watch } from 'vue'
,然后就可以写在setup()
里面了;
既要指明监视的属性,也要指明监视的回调;
监视时的各种情况:
- ref定义的单个响应式数据:
watch(被监视的值,回调函数)
; 回调函数依次接收newValue
、oldValue
,不用return
;不能用.value
,因为会变成监视一个基本数据值而不是监视这个变量,就会没有反应。 - ref定义的多个响应式数据:
写多个watch(被监视的值,回调函数)
; 回调函数依次接收newValue
、oldValue
,不用return
⟶ \longrightarrow ⟶Vue2中watch
是配置项,只能写一次,Vue3中watch
是一个方法,所以可以多次调用;
也可以watch( [被监视的值1, 被监视的值2, ...],回调函数)
;回调接收到的newValue
、oldValue
也会是一个数组,顺序和传入watch
的被监视的数组一样; - reactive定义的响应式数据的全部属性:
强制开启深度监视,deep配置无效 (关不掉哦);oldValue会和newValue一样,无法正确获取(谁让它是个代理对象呢);没什么办法改这个问题,要不就改watch的源码,新开一块空间记录旧的值; - reactive定义的响应式数据的某个属性:
watch( ()=>被监视的值,回调函数)
; deep配置有效;如果这个属性也是数组或对象,那么需要开启深度监听; - reactive定义的响应式数据的某些属性:
watch( ()=>[被监视的值1, 被监视的值2, ...],回调函数)
; deep配置有效,同上;
算了看视频吧,有点多
1.4.3. watchEffect
没有指定具体的监视对象,而是回调函数中用到了谁就监视谁;
直接传入回调函数,并且没有newValue
和oldValue
:watchEffect(()=>{})
;
有点像computed
,但是computed
注重计算出来的值,必须return
返回值,而watchEffect
更注重过程,不用return
返回值。
1.5. hook
hook本质是一个函数,把setup()中使用的组合式API进行了封装,类似于Vue2中的mixin;hook复用代码能让setup中的逻辑更清楚易懂。
1.6. 生命周期
名称
Vue3可以继续使用Vue2中的生命周期钩子,但是有两个改了名字:
beforeDestroy
→
\rightarrow
→ beforeUnmount
destroyed
→
\rightarrow
→ unmounted
组合式API
除了beforeCreate
和created
外,可以把其他生命周期钩子用替代方法在setup()中使用:
beforeMount
→
\rightarrow
→ onBeforeMount
mounted
→
\rightarrow
→ onmounted
beforeUpdate
→
\rightarrow
→ onBeforeUpdate
updated
→
\rightarrow
→ onUpdated
beforeUnmount
→
\rightarrow
→ onBeforeUnmount
unmounted
→
\rightarrow
→ onUnmounted
用于替代beforeCreate
和created
的方法就是setup()
本身;
记得提前引入这些API:import { ... } from ‘’vue
;
它们都是函数,传入的参数是一个回调函数。
如果同时用了组合式API和配置项两种方式调用生命周期钩子,那么组合式API的执行时机要比配置项的早一些。最好别这么干,挺乱的。
1.7. toRef
创建一个ref对象,它的value指向另一个对象中的某个属性;
举例 const name = toRef( person, 'name');
用于将响应式对象的某个属性单独提供给外部使用;也可以用toRefs(person)
把person中第一层的每一个属性都拆出来,找一个空对象用扩展运算符展开就可以用了{ ...toRefs(person) }
,省一层名字。
1.8. shallowReactive 与 shallowRef
shallowReactive()
:浅层响应式,只处理对象第一层的数据;
shallowRef()
:传入基本数据类型时,和ref的效果一样;传入对象时,就不处理了诶,只有把整个对象换掉才会触发响应式
→
\rightarrow
→ 比如let x = ref({y:1})
展开x后会看到value
里是一个Proxy
类型的{y:1}
,传入的对象变成了一个代理对象,成为了响应式数据;而let x = shallowRef({y:1})
展开x后value
里是一个普普通通的Object
,此时只有x是响应式的,传入对象不是,所以修改x.y
时无事发生。
⟶
\longrightarrow
⟶
如果有一个对象数据,结构比较深,但只有最外层的属性会发生变化,可以用shallowReactive()
;如果有一个对象数据,后续功能不会修改它的某个属性,而是生成一个新对象换掉它,可以用shallowRef()
;
1.9. readonly 与 shallowReadonly
readonly()
接受一个响应式数据,返回一个新的响应式数据覆盖原来的数据,把它保护起来,不能被修改(不是改了数据Vue监控不到,而是直接不让改);
shallowReadonly()
和readonly()
差不多,只不过只保护对象第一层的数据。
应用场景: 其他组件传过来的响应式数据,只让你使用不让你修改,就可以用这两个API保护起来;或者项目开发时前期需要响应,后期不可修改,也可以用它们。
1.10. toRaw 与 markRow
toRaw()
:将一个由reactive
生成的响应式对象转为普通对象;用于读取响应式对象对应的普通对象,对这个普通对象的所有操作都不会引起页面更新;
markRow()
:标记一个对象,使其永远不会再成为响应式对象;用于不应该被设置为响应式的对象(如复杂的第三方类库);用于渲染具有不可变数据源的大型列表时跳过响应式转换以提高性能,也就是无需承担代理访问/追踪的开销。
1.11. customRef
创建一个自定义的 ref,显式声明对其依赖追踪和更新触发的控制方式。
customRef()
接收一个工厂函数作为参数,这个工厂函数接受 track
和 trigger
两个函数作为参数,并返回一个带有 get
和 set
方法的对象;track()
应该在 get()
方法中return
之前调用,而 trigger()
应该在 set()
中数据更新后调用;
**举例:**创建一个防抖 ref,即只在最近一次 set 调用后的一段固定间隔后再调用
import { customRef } from 'vue'
export function useDebouncedRef(value, delay = 200) {
let timeout
return customRef((track, trigger) => {
return {
get() {
track()
return value
},
set(newValue) {
clearTimeout(timeout)
timeout = setTimeout(() => {
value = newValue
trigger()
}, delay)
}
}
})
}
1.12. provide 和 inject
实现祖先和后代组件间通信,在祖先组件中用provide
选项提供一些属性或方法,后代组件中用inject
进行接收并使用。
- provide()
如果没有使用< script setup >
,那么需要在setup()
同步调用;
提供的响应式状态使后代组件可以由此和提供者建立响应式的联系;
接收两个参数:
第一个参数是注入名,可以是一个字符串或是一个Symbol
;后代组件会用注入名来查找期望注入的值;一个组件可以多次调用provide()
,使用不同的注入名,注入不同的依赖值;
第二个参数是提供的值,值可以是任意类型,包括响应式的状态,比如一个ref
; - inject()
如果没有使用< script setup >
,那么需要在 setup() 内同步调用;
举例:setup() { const message = inject('message') return {message } }
如果提供的值是一个ref
,注入进来的会是该ref
对象,而不会自动解包为其内部的值;这使得注入方组件能够通过ref
对象保持了和供给方的响应性链接;
默认情况下,inject
假设传入的注入名会被某个祖先链上的组件提供;
如果该注入名的确没有任何组件提供,则会抛出一个运行时警告;
如果在注入一个值时不要求必须有提供者,那么我们应该声明一个默认值,和 props 类似const value = inject('message', '默认值')
;
当提供 / 注入响应式的数据时,建议尽可能将任何对响应式状态的变更都保持在供给方组件中。这样可以确保所提供状态的声明和变更操作都内聚在同一个组件内,使其更容易维护。
1.13. 响应式数据的判断
isRef()
检查一个值是否为ref
对象;isReactive()
检查一个值是否为reactive
创建的响应式代理;isReadonly()
检查一个值是否为readonly
创建的只读代理;isProxy()
检查一个值是否为reactive
或者readonly
创建的代理;一个被readonly
处理过的响应式对象类型仍然是Proxy
,只是不能改了;
1.14. options API(Vue2) 和 composition API(Vue3)
options API中,每个功能的data、methods、computed都被拆开了,放在对应的配置项里,阅读和维护很麻烦;composition API 可以使用hook
函数将每一个功能的data、methods、computed等封装到一起,在组件中只要引入就可以使用了,找起来很方便。
二. fragment组件
在Vue2中,组件必须有一个根标签,Vue3中可以不写根标签,但实际上是把组件中的标签都包含在了一个Fragment虚拟元素中,以减小标签层级和内存占用。
三. Teleport
传送+闪现,把< teleport to="x"> < /teleport >
里的内容传送到x
里,x
可以是标签、CSS选择器。
四. Suspense
静态引入一个组件:import Child from './components/Child.vue'
动态引入一个组件:import { defineAsyncComponent } from 'vue'
,const Child = defineAsyncComponent(()=>import( './components/Child.vue' ))
静态引入组件时,如果有嵌套了好几层的结构,那么会等最里面的组件加载完成后外面的组件才能一起显示;
而动态引入时将里面的组件设为异步方式引入,外层的组件不需要等待就可以被渲染出来,但是这时内层组件还没有完成加载,没有渲染,可能让用户误认为页面已经没有需要加载的东西了,此时可以使用Suspense
:
<Suspense>
<template v-slot:default> // v-slot:default 一个字母都不能改,底层用的就是这个名字的插槽
<Child /> // 此处放置异步加载的组件
</template>
<template v-slot:fallback>// v-slot:fallback 一个字母都不能改,底层用的就是这个名字的插槽
<h3>组件加载中... </h3> // 此处放置组件没加载出来的时候,该显示哪些内容
</template>
</Suspense>
五. Vue3和Vue2的不同
5.1. 全局API的转移
将一些Vue.xxx
调整为app.xxx
:
全局API (Vue) | 实例API (app) |
---|---|
Vue.config.xxx | app.config.xxx |
Vue.config.productionTip | 移除 |
Vue.component | app.component |
Vue.directive | app.directive |
Vue.mixin | app.mixin |
Vue.use | app.use |
Vue.prototype | app.config.globalProperties |
5.2. 其他不同
- data选项应该始终声明为一个函数;
- 过渡样式类名的修改:
v-enter
⟶ \longrightarrow ⟶v-enter-from
,v-leave
⟶ \longrightarrow ⟶v-leave-from
- 移除
keyCode
作为v-on
的修饰符,同时不再支持config.keyCodes
- 移除
v-on.native
修饰符
Vue3需要在emits
选项中接收父组件传过来的事件,如果没有进行接收那么默认为原生事件; - 移除过滤器
filter