Vue
前言
从Vue3发布以来,陆陆续续也有很多项目慢慢的从vue2往vue3转变,特别是vue3.2退出setup语法糖之后,很大层度的简化了vue3的书写,下面我们来看一下,vue2、vue3、和vue3setup语法糖三种写法的区别。
1、点击切换页面数据实例对比
1.1、vue2
<template><div @click="changeMsg">{{msg}}</div></template><script>
export default{data(){return{
msg:'hello world'}},
methods:{changeMsg(){
this.msg ='hello juejin'}}}</script>
1.2、vue3
<template><div @click="changeMsg">{{msg}}</div></template><script>
import { ref,defineComponent } from "vue";
export defaultdefineComponent({setup(){const msg =ref('hello world')const changeMsg =()=>{
msg.value ='hello juejin'}return{
msg,
changeMsg
};},});</script>
1.3、vue3 setup语法糖
<template><div @click="changeMsg">{{ msg }}</div></template><script setup>
import { ref } from "vue";const msg =ref('hello world')const changeMsg =()=>{
msg.value ='hello juejin'}</script>
总结:
vue2是将data和methods包括后面的watch,computed等分开管理;
vue3是将相关逻辑放到了一起(类似于原生js开发);
setup语法糖则可以让变量方法不用再写return,后面的组件甚至是自定义指令也可以在我们的template中自动获得。
2、ref 和 reactive
vue2中data函数中的数据都具有响应式,页面会随着data中的数据变化而变化;而vue3不存在data函数,所以Vue3引入了ref和reactive函数来将使得变量成为响应式的数据
2.1、vue2
<script>
export default{data(){return{
msg:'hello world'
obj:{
name:'juejin',
age:3}}},
methods:{changeData(){
this.msg ='hello juejin',
this.obj.name ='hello world'}}}</script>
2.2、vue3
<script>
import { ref,reactive,defineComponent } from "vue";
export defaultdefineComponent({setup(){
let msg =ref('hello world')
let obj =reactive({
name:'juejin',
age:3})const changeData =()=>{
msg.value ='hello juejin'
obj.name ='hello world'}return{
msg,
obj,
changeData
};},});</script>
2.3、vue3 setup语法糖
<script setup>
import { ref,reactive } from "vue";
let msg =ref('hello world')
let obj =reactive({
name:'juejin',
age:3})const changeData =()=>{
msg.value ='hello juejin'
obj.name ='hello world'}</script>
总结:
使用ref的时候在js中取值的时候需要加上.value。
reactive更推荐去定义复杂的数据类型 ref 更推荐定义基本类型
3、生命周期
Vue2和Vue3生命周期的差异
Vue2Vue3(setup)描述beforeCreate-实例创建前beforeCreate-实例创建后beforeMountonBeforeMountDOM挂载前调用mountedonMountedDOM挂载完成调用beforeUpdateonBeforeUpdate数据更新之前被调用updatedonUpdated数据更新之后被调用beforeDestroyonBeforeUnmount组件销毁前调用destroyedonUnmounted组件销毁完成调用
4、使用mounted做对比
4.1、vue2
<script>
export default{mounted(){
console.log('挂载完成')}}</script>
4.2、vue3
<script>
import { onMounted,defineComponent } from "vue";
export defaultdefineComponent({setup(){onMounted(()=>{
console.log('挂载完成')})return{
onMounted
};},});</script>
4.3、vue3 setup语法糖
<script setup>
import { onMounted } from "vue";onMounted(()=>{
console.log('挂载完成')})</script>
总结:Vue3中的生命周期相对于Vue2做了一些调整,命名上发生了一些变化并且移除了beforeCreate和created,因为setup是围绕beforeCreate和created生命周期钩子运行的,所以不再需要它们。
5、watch和computed
5.1、vue2
<template><div>{{ addSum }}</div></template><script>
export default{data(){return{
a:1,
b:2}},
computed:{addSum(){return this.a + this.b
}},
watch:{a(newValue, oldValue){
console.log(`a从${oldValue}变成了${newValue}`)}}}</script>
5.2、vue3
<template><div>{{addSum}}</div></template><script>
import { computed, ref, watch, defineComponent } from "vue";
export defaultdefineComponent({setup(){const a =ref(1)const b =ref(2)
let addSum =computed(()=>{return a.value+b.value
})watch(a,(newValue, oldValue)=>{
console.log(`a从${oldValue}变成了${newValue}`)})return{
addSum
};},});</script>
5.3、vue3 setup语法糖
<template><div>{{ addSum }}</div></template><script setup>
import { computed, ref, watch } from "vue";const a =ref(1)const b =ref(2)
let addSum =computed(()=>{return a.value + b.value
})watch(a,(newValue, oldValue)=>{
console.log(`a从${oldValue}变成了${newValue}`)})</script>
Vue3中除了watch,还引入了副作用监听函数watchEffect;
watchEffect它会立即执行传入的一个函数,同时响应式追踪其依赖,并在其依赖变更时重新运行该函数。
例如:
<template><div>{{ watchTarget }}</div></template><script setup>
import { watchEffect,ref } from "vue";const watchTarget =ref(0)watchEffect(()=>{
console.log(watchTarget.value)})setInterval(()=>{
watchTarget.value++},1000)</script>
首先刚进入页面就会执行watchEffect中的函数打印出:0,随着定时器的运行,watchEffect监听到依赖数据的变化回调函数每隔一秒就会执行一次
总结:
computed和watch所依赖的数据必须是响应式的。Vue3引入了watchEffect,watchEffect 相当于将 watch 的依赖源和回调函数合并,当任何你有用到的响应式依赖更新时,该回调函数便会重新执行。不同于 watch的是watchEffect的回调函数会被立即执行,即({ immediate: true })
6、组件通信
Vue2Vue3(setup)描述propsprops父传子$emitemits子传父$attrsattrs父传子$listeners无(合并到 attrs方式)子传父provideprovide父传子injectinject子传父$parent无子组件访问父组件$children无父组件访问子组件$refexpose&ref父组件访问子组件EventBusmitt兄弟传值
7、props传值
props是组件通信中最常用的通信方式之一。父组件通过v-bind传入,子组件通过props接收,下面是它的三种实现方式
7.1、vue2
//父组件<template><div><Child :msg="parentMsg"/></div></template><script>
import Child from './Child'
export default{
components:{
Child
},data(){return{
parentMsg:'父组件信息'}}}</script>//子组件<template><div>{{msg}}</div></template><script>
export default{
props:['msg']}</script>
7.2、vue3
//父组件<template><div><Child :msg="parentMsg"/></div></template><script>
import { ref,defineComponent } from 'vue'
import Child from './Child.vue'
export defaultdefineComponent({
components:{
Child
},setup(){const parentMsg =ref('父组件信息')return{
parentMsg
};},});</script>//子组件<template><div>{{ parentMsg }}</div></template><script>
import { defineComponent,toRef } from "vue";
export defaultdefineComponent({
props:["msg"],// 如果这行不写,下面就接收不到setup(props){
console.log(props.msg)//父组件信息
let parentMsg =toRef(props,'msg')return{
parentMsg
};},});</script>
7.3、vue3 setup语法糖
//父组件<template><div><Child :msg="parentMsg"/></div></template><script setup>
import { ref } from 'vue'
import Child from './Child.vue'const parentMsg =ref('父组件信息')</script>//子组件<template><div>{{ parentMsg }}</div></template><script setup>
import { toRef, defineProps } from "vue";const props =defineProps(["msg"]);
console.log(props.msg)//父组件信息
let parentMsg =toRef(props,'msg')</script>
总结:props中数据流是单项的,即子组件不可改变父组件传来的值
在组合式API中,如果想在子组件中用其它变量接收props的值时需要使用toRef将props中的属性转为响应式。
8、emit
子组件可以通过emit发布一个事件并传递一些参数,父组件通过v-onj进行这个事件的监听
8.1、vue2
//父组件<template><div><Child @sendMsg="getFromChild"/></div></template><script>
import Child from './Child'
export default{
components:{
Child
},
methods:{getFromChild(val){
console.log(val)//我是子组件数据}}}</script>// 子组件<template><div><button @click="sendFun">send</button></div></template><script>
export default{
methods:{sendFun(){
this.$emit('sendMsg','我是子组件数据')}}}</script>
8.2、vue3
//父组件<template><div><Child @sendMsg="getFromChild"/></div></template><script>
import Child from './Child'
import { defineComponent } from "vue";
export defaultdefineComponent({
components:{
Child
},setup(){const getFromChild =(val)=>{
console.log(val)//我是子组件数据}return{
getFromChild
};},});</script>//子组件<template><div><button @click="sendFun">send</button></div></template><script>
import { defineComponent } from "vue";
export defaultdefineComponent({
emits:['sendMsg'],setup(props, ctx){const sendFun =()=>{
ctx.emit('sendMsg','我是子组件数据')}return{
sendFun
};},});</script>
8.3、vue3 setup语法糖
//父组件<template><div><Child @sendMsg="getFromChild"/></div></template><script setup>
import Child from './Child'const getFromChild =(val)=>{
console.log(val)//我是子组件数据}</script>//子组件<template><div><button @click="sendFun">send</button></div></template><script setup>
import { defineEmits } from "vue";const emits =defineEmits(['sendMsg'])const sendFun =()=>{emits('sendMsg','我是子组件数据')}</script>
9、attrs和listeners
子组件使用$attrs可以获得父组件除了props传递的属性和特性绑定属性 (class和 style)之外的所有属性。
子组件使用$listeners可以获得父组件(不含.native修饰器的)所有v-on事件监听器,在Vue3中已经不再使用;但是Vue3中的attrs不仅可以获得父组件传来的属性也可以获得父组件v-on事件监听器
9.1、vue2
//父组件<template><div><Child @parentFun="parentFun":msg1="msg1":msg2="msg2"/></div></template><script>
import Child from './Child'
export default{
components:{
Child
},data(){return{
msg1:'子组件msg1',
msg2:'子组件msg2'}},
methods:{parentFun(val){
console.log(`父组件方法被调用,获得子组件传值:${val}`)}}}</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script>
export default{
methods:{getParentFun(){
this.$listeners.parentFun('我是子组件数据')}},created(){//获取父组件中所有绑定属性
console.log(this.$attrs)//{"msg1": "子组件msg1","msg2": "子组件msg2"}//获取父组件中所有绑定方法
console.log(this.$listeners)//{parentFun:f}}}</script>
9.2、vue3
//父组件<template><div><Child @parentFun="parentFun":msg1="msg1":msg2="msg2"/></div></template><script>
import Child from './Child'
import { defineComponent,ref } from "vue";
export defaultdefineComponent({
components:{
Child
},setup(){const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')const parentFun =(val)=>{
console.log(`父组件方法被调用,获得子组件传值:${val}`)}return{
parentFun,
msg1,
msg2
};},});</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script>
import { defineComponent } from "vue";
export defaultdefineComponent({
emits:['sendMsg'],setup(props, ctx){//获取父组件方法和事件
console.log(ctx.attrs)//Proxy {"msg1": "子组件msg1","msg2": "子组件msg2"}const getParentFun =()=>{//调用父组件方法
ctx.attrs.onParentFun('我是子组件数据')}return{
getParentFun
};},});</script>
9.3、vue3 setup语法糖
//父组件<template><div><Child @parentFun="parentFun":msg1="msg1":msg2="msg2"/></div></template><script setup>
import Child from './Child'
import { ref } from "vue";const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')const parentFun =(val)=>{
console.log(`父组件方法被调用,获得子组件传值:${val}`)}</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script setup>
import { useAttrs } from "vue";const attrs =useAttrs()//获取父组件方法和事件
console.log(attrs)//Proxy {"msg1": "子组件msg1","msg2": "子组件msg2"}const getParentFun =()=>{//调用父组件方法
attrs.onParentFun('我是子组件数据')}</script>
注意:Vue3中使用attrs调用父组件方法时,方法前需要加上on;如parentFun->onParentFun
10、provide/inject
provide:是一个对象,或者是一个返回对象的函数。里面包含要给子孙后代属性
inject:一个字符串数组,或者是一个对象。获取父组件或更高层次的组件provide的值,既在任何后代组件都可以通过inject获得
10.1、vue2
//父组件<script>
import Child from './Child'
export default{
components:{
Child
},data(){return{
msg1:'子组件msg1',
msg2:'子组件msg2'}},provide(){return{
msg1: this.msg1,
msg2: this.msg2
}}}</script>//子组件<script>
export default{
inject:['msg1','msg2'],created(){//获取高层级提供的属性
console.log(this.msg1)//子组件msg1
console.log(this.msg2)//子组件msg2}}</script>
10.2、vue3
//父组件<script>
import Child from './Child'
import { ref, defineComponent,provide } from "vue";
export defaultdefineComponent({
components:{
Child
},setup(){const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')provide("msg1", msg1)provide("msg2", msg2)return{}},});</script>//子组件<template><div><button @click="getParentFun">调用父组件方法</button></div></template><script>
import { inject, defineComponent } from "vue";
export defaultdefineComponent({setup(){
console.log(inject('msg1').value)//子组件msg1
console.log(inject('msg2').value)//子组件msg2},});</script>
10.3、vue3 setup语法糖
//父组件<script setup>
import Child from './Child'
import { ref,provide } from "vue";const msg1 =ref('子组件msg1')const msg2 =ref('子组件msg2')provide("msg1",msg1)provide("msg2",msg2)</script>//子组件<script setup>
import { inject } from "vue";
console.log(inject('msg1').value)//子组件msg1
console.log(inject('msg2').value)//子组件msg2</script>
provide/inject一般在深层组件嵌套中使用合适。一般在组件开发中用的居多。
11、parent/children
$parent: 子组件获取父组件Vue实例,可以获取父组件的属性方法等
$children: 父组件获取子组件Vue实例,是一个数组,是直接儿子的集合,但并不保证子组件的顺序
11.1、vue2
import Child from './Child'
export default{
components:{
Child
},created(){
console.log(this.$children)//[Child实例]
console.log(this.$parent)//父组件实例}}
12、expose&ref
$refs可以直接获取元素属性,同时也可以直接获取子组件实例
12.1、vue2
//父组件<template><div><Child ref="child"/></div></template><script>
import Child from './Child'
export default{
components:{
Child
},mounted(){//获取子组件属性
console.log(this.$refs.child.msg)//子组件元素//调用子组件方法
this.$refs.child.childFun('父组件信息')}}</script>//子组件 <template><div><div></div></div></template><script>
export default{data(){return{
msg:'子组件元素'}},
methods:{childFun(val){
console.log(`子组件方法被调用,值${val}`)}}}</script>
12.2、vue3
//父组件<template><div><Child ref="child"/></div></template><script>
import Child from './Child'
import { ref, defineComponent, onMounted } from "vue";
export defaultdefineComponent({
components:{
Child
},setup(){const child =ref()//注意命名需要和template中ref对应onMounted(()=>{//获取子组件属性
console.log(child.value.msg)//子组件元素//调用子组件方法
child.value.childFun('父组件信息')})return{
child //必须return出去 否则获取不到实例}},});</script>//子组件<template><div></div></template><script>
import { defineComponent, ref } from "vue";
export defaultdefineComponent({setup(){const msg =ref('子组件元素')const childFun =(val)=>{
console.log(`子组件方法被调用,值${val}`)}return{
msg,
childFun
}},});</script>
12.3、vue3 setup语法糖
//父组件<template><div><Child ref="child"/></div></template><script setup>
import Child from './Child'
import { ref, onMounted } from "vue";const child =ref()//注意命名需要和template中ref对应onMounted(()=>{//获取子组件属性
console.log(child.value.msg)//子组件元素//调用子组件方法
child.value.childFun('父组件信息')})</script>//子组件<template><div></div></template><script setup>
import { ref,defineExpose } from "vue";const msg =ref('子组件元素')const childFun =(val)=>{
console.log(`子组件方法被调用,值${val}`)}//必须暴露出去父组件才会获取到defineExpose({
childFun,
msg
})</script>
注意:
通过ref获取子组件实例必须在页面挂载完成后才能获取。
在使用setup语法糖时候,子组件必须元素或方法暴露出去父组件才能获取到
13、EventBus/mitt
兄弟组件通信可以通过一个事件中心EventBus实现,既新建一个Vue实例来进行事件的监听,触发和销毁。
在Vue3中没有了EventBus兄弟组件通信,但是现在有了一个替代的方案mitt.js,原理还是 EventBus
13.1、vue2
//组件1<template><div><button @click="sendMsg">传值</button></div></template><script>
import Bus from './bus.js'
export default{data(){return{
msg:'子组件元素'}},
methods:{sendMsg(){
Bus.$emit('sendMsg','兄弟的值')}}}</script>//组件2<template><div>
组件2</div></template><script>
import Bus from './bus.js'
export default{created(){
Bus.$on('sendMsg',(val)=>{
console.log(val);//兄弟的值})}}</script>//bus.js
import Vue from "vue"
export default new Vue()
13.2、vue3
首先安装mitt
npm i mitt -S
然后像Vue2中bus.js一样新建mitt.js文件
mitt.js
import mitt from'mitt'const Mitt =mitt()exportdefault Mitt
//组件1<template><button @click="sendMsg">传值</button></template><script>
import { defineComponent } from "vue";
import Mitt from './mitt.js'
export defaultdefineComponent({setup(){const sendMsg =()=>{
Mitt.emit('sendMsg','兄弟的值')}return{
sendMsg
}},});</script>//组件2<template><div>
组件2</div></template><script>
import { defineComponent, onUnmounted } from "vue";
import Mitt from './mitt.js'
export defaultdefineComponent({setup(){const getMsg =(val)=>{
console.log(val);//兄弟的值}
Mitt.on('sendMsg', getMsg)onUnmounted(()=>{//组件销毁 移除监听
Mitt.off('sendMsg', getMsg)})},});</script>
13.3、vue3 setup语法糖
//组件1<template><button @click="sendMsg">传值</button></template><script setup>
import Mitt from './mitt.js'const sendMsg =()=>{
Mitt.emit('sendMsg','兄弟的值')}</script>//组件2<template><div>
组件2</div></template><script setup>
import { onUnmounted } from "vue";
import Mitt from './mitt.js'const getMsg =(val)=>{
console.log(val);//兄弟的值}
Mitt.on('sendMsg', getMsg)onUnmounted(()=>{//组件销毁 移除监听
Mitt.off('sendMsg', getMsg)})</script>
14、v-model和sync
-model大家都很熟悉,就是双向绑定的语法糖。这里不讨论它在input标签的使用;只是看一下它和sync在组件中的使用
我们都知道Vue中的props是单向向下绑定的;每次父组件更新时,子组件中的所有props都会刷新为最新的值;但是如果在子组件中修改 props ,Vue会向你发出一个警告(无法在子组件修改父组件传递的值);可能是为了防止子组件无意间修改了父组件的状态,来避免应用的数据流变得混乱难以理解。
但是可以在父组件使用子组件的标签上声明一个监听事件,子组件想要修改props的值时使用$emit触发事件并传入新的值,让父组件进行修改。
为了方便vue就使用了v-model和sync语法糖。
14.1、vue2
//父组件<template><div><!--
完整写法
<Child :msg="msg" @update:changePval="msg=$event"/>--><Child :changePval.sync="msg"/>{{msg}}</div></template><script>
import Child from './Child'
export default{
components:{
Child
},data(){return{
msg:'父组件值'}}}</script>//子组件<template><div><button @click="changePval">改变父组件值</button></div></template><script>
export default{data(){return{
msg:'子组件元素'}},
methods:{changePval(){//点击则会修改父组件msg的值
this.$emit('update:changePval','改变后的值')}}}</script>
14.2、vue3 setup语法糖
//父组件<template><div><!--
完整写法
<Child :msg="msg" @update:changePval="msg=$event"/>--><Child v-model:changePval="msg"/>{{msg}}</div></template><script setup>
import Child from './Child'
import { ref } from 'vue'const msg =ref('父组件值')</script>//子组件<template><button @click="changePval">改变父组件值</button></template><script setup>
import { defineEmits } from 'vue';const emits =defineEmits(['changePval'])const changePval =()=>{//点击则会修改父组件msg的值emits('update:changePval','改变后的值')}</script>
总结:
vue3中移除了sync的写法,取而代之的式v-model:event的形式
其v-model:changePval="msg"或者:changePval.sync="msg"的完整写法为:msg=“msg” @update:changePval=“msg=$event”。
所以子组件需要发送update:changePval事件进行修改父组件的值
15、路由
vue3和vue2路由常用功能只是写法上有些区别
15.1、vue2
<template><div><button @click="toPage">路由跳转</button></div></template><script>
export default{beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),
methods:{toPage(){//路由跳转
this.$router.push(xxx)}},created(){//获取params
this.$router.params
//获取query
this.$router.query
}}</script>
15.2、vue3
<template><div><button @click="toPage">路由跳转</button></div></template><script>
import { defineComponent } from 'vue'
import { useRoute, useRouter } from 'vue-router'
export defaultdefineComponent({beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),beforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()}),setup(){const router =useRouter()const route =useRoute()const toPage =()=>{
router.push(xxx)}//获取params 注意是route
route.params
//获取query
route.query
return{
toPage
}},});</script>
15.3、vue3 setup语法糖
我之所以用beforeRouteEnter作为路由守卫的示例是因为它在setup语法糖中是无法使用的;大家都知道setup中组件实例已经创建,是能够获取到组件实例的。而beforeRouteEnter是再进入路由前触发的,此时组件还未创建,所以是无法setup中的;如果想在setup语法糖中使用则需要再写一个setup语法糖的script 如下:
<template><div><button @click="toPage">路由跳转</button></div></template><script>
export default{beforeRouteEnter(to, from, next){// 在渲染该组件的对应路由被 confirm 前调用next()},};</script><script setup>
import { useRoute, useRouter,onBeforeRouteLeave, onBeforeRouteUpdate } from 'vue-router'const router =useRouter()const route =useRoute()const toPage =()=>{
router.push(xxx)}//获取params 注意是route
route.params
//获取query
route.query
//路由守卫onBeforeRouteUpdate((to, from, next)=>{//当前组件路由改变后,进行触发next()})onBeforeRouteLeave((to, from, next)=>{//离开当前的组件,触发next()})</script>
provide/inject一般在深层组件嵌套中使用合适。一般在组件开发中用的居多。
版权归原作者 smileAgain-lg 所有, 如有侵权,请联系我们删除。