随着2023年末vue官方宣布vue2日后不在更新,vue3将成为日后开发框架的新首选项。特此记录下vue2与vue3中的语法,常用插件等使用差异
一,api及语法
1,双向绑定原理
Vue2使用Object.defineProperty 为每个属性创建getter和setter,通过getter和setter来捕获操作以实现响应式更新; 很多情况下,属性的新增和删除拦截不到(比如数组的长度变化)
Vue3 通过Proxy实现,proxy是ES6新特性,提供了更多的拦截操作;能监听到属性的删除和新增
v2 | 使用object.defineProperty来劫持数据的setter和getter方法,对象改变需要借助api去深度监听 |
v3 | Proxy来实现数据劫持,删除了一些api($on,$once,$off) fiter等,优化了Block tree,solt,diff 算法等 |
Vue2在data中定义的数据就会自动遍历绑定Object.defineProperty以实现响应式;Vue3中要用ref包装,通过返回值的 .value
属性获取响应式的值 ,修改也需要对 .value进行修改
(注意在js中要.value,在模板html中解析时会自动添加.value,不需要添加)
<template>
<div>
<!-- 不需要.value -->
{{ testOne }}
{{ testTwo.directionD}}
</div>
</template>
<script setup>
const testOne= ref(0)
const testTwo= ref({
directionD: '',
directionA: '',
arr: []
})
const dataThree= ref({})
// 使用需要.value
console.log(testOne.value);
console.log(dataThree.value);
console.log(testTwo.value.directionD);
console.log(testTwo.value.directionD);
</script>
除了ref()之外,还有reative也能实现响应式(reative不加.value)
二者用法差异不在此赘述,可见大佬文章:http://t.csdnimg.cn/wpu6r
2,vue3支持碎片化
v2中只能存在一个根节点,v3中可以保持如下多个根节点,一定程度上减少了标签的层级
注意:下列情况若子组件中存在多个根节点, 则使用组件时不能使用 v-show(无法对子组件根节点添加display)
解决: 1, 可用v-if代替(可能存在首次渲染失效) 2,只保留一个根节点
<template>
<div class="container">
<!-- 此时v-show无效 -->
<son v-show="false"></son>
</div>
</template>
-----son 组件
<template>
<div class="son_container">
1
</div>
<div class="son_container2">
2
</div>
<div class="son_container3">
3
</div>
</template>
3,生命周期
Vue2 | Vue3 | |
beforeCreate() | setup() | 组件开始创建数据实例之前 |
created() | setup() | 组件实例完成 |
beforeMount() | onBeforeAMount() | DOM挂载之前 |
mounted() | onMounted() | DOM挂载完成 |
beforeUpdate() | onBeforeUpdate() | 组件更新之前 |
undated() | onUpdated() | 组件更新之后 |
beforeDestroy() | onBeforeunmount() | 组件销毁之前 |
destroyed() | onUnmounted() | 组件销毁之后 |
大部分生命周期在vue2的周期前加 on 即可;vue3没有beforeCreate 和 created两个周期,那想在这两个周期中进行逻辑处理怎么办呢? setup中写好了调用即可👇
4, 去除this
Vue3中没有this, 使用this报错 需要组件内的某个方法直接使用即可(注意使用的数据必须在调用前定义)
5,组件传值props和emit
Vue2中是 props和 this.$emit Vue3中则是[defineEmits defineProps] props emit;
需要注意的是 Vue2中传值给子组件可以的属性可以直接使用,Vue3中子组件接收的值在props对象中,所以在ts/js中使用需要 props.name
emit触发的事件,需要defineProps声明接收数据,defineEmits 声明以明确事件定义
// 父组件中使用子组件
<son ref="bottomContract" @transferData="transferData" @reloadEcharts="reloadEcharts" :dataOne="tableTime" :dataTwo="echartsColumnData" :dataThree="dataThree" />
// 子组件
<script lang="ts" setup>
//子组件中接收
const props = defineProps({
dataOne: {
default: () => 0
},
dataThree: {
type: Number,
default: () => 0
},
dataTwo: {
default: () => []
}
})
// 也可以数组形式
// const emit = defineEmits(["dataFour", "dataFive", "dataSix"])
// 子组件中使用
const test = () => {
console.log(props.dataOne)
}
// defineEmits明确事件定义
const emit = defineEmits(["transferData", "reloadEcharts"])
const testTwo = ()=> {
emit("transferData", 'value')
emit("transferData", 'value')
}
</script>
6,watch和computed
watch
watch,computed与vue2用法差别不大,写法改变了; vue2是将要监听的值放在watch:{ }中
Vue3,监听watch第一个参数是直接传入要监听的对象 ;深度监听复杂对象 {deep: true}
const demo = reactive({
name: '前端',
nickName: '1',
Yiqian: {
name: '',
nickName: ''
}
})
// 深度监听复杂对象 {deep: true}
watch(demo, (newValue, oldValue) => {
console.log('watch 已触发', newValue)
}, {deep: true})
// 也可以只监听其中的某个属性
watch(() => ({ ...demo }), (newValue, oldValue) => {
console.log('watch 已触发', newValue)
})
监听一个属性就要用一个watch,是不是不太妙? 那当然也可以组合到一起,此时的第一个参数是一个数组,第二参数箭头函数的参数也是数组的形式,按照数组下标对应监听值
watch(() => [demo.name, demo.nickname], (newValue, oldValue) => {
console.log(newValue); // 此时newValue是数组,按照数据下标获取对应监听属性值
console.log(newValue[0])
console.log(newValue[1])
})
computed
与vue2的computed配置写法基本一致
const user = ref({
testOne: 'A',
testTwo: 'B'
});
// 只有getter的计算属性
const fullName1 = computed(() => {
return user.value.testOne+ '-' + user.value.testTwo;
})
// 有getter与setter的计算属性
const fullName2 = computed({
get () {
return user.value.testOne+ '-' + user.value.testTwo;
},
set (value: string) {
const names ='111';
user.value.testOne= names;
user.value.testTwo= names;
}
});
7,子组件实例,调用组件方法
Vue2中 子组件定义ref="name"后使用this.$refs.name 就能拿到组件name的实例;同时可以this.$refs.name.test() 的方式直接调用子组件的test()方法
Vue3中,子组件定义ref="name",需要用ref()来定义引用,将其绑定到对应子组件上;若想直接调用子组件的方法,需要在子组件中defineExpose
显示暴露出对应的方法(组件封装性),若不暴露出来则子组件实例上不会存在此方法
// -----父组件------
<template>
<ChildComponent ref="timingEchartsModule" />
</template>
<script setup>
import { ref } from 'vue';
import ChildComponent from './ChildComponent.vue';
// 定义一个 ref 来引用子组件实例
const timingEchartsModule = ref();
// 在某个生命周期钩子中访问子组件实例
onMounted(() => {
console.log(timingEchartsModule.value);
timingEchartsModule.value.test() //调用子组件方法
});
</script>
// ----子组件----
<script setup>
const test= () => {
console.log('someMethod called');
};
// defineExpose暴露方法 若不暴露,此方法不会存在组件实例上
defineExpose({
test
})
</script>
8,选项式api和组合式api
Vue2中 选项式的api,创建组件时需要使用各种选项 data methods watch等
Vue3组合式允许将相关的代码逻辑放在一起处理,让代码更易于理解和维护
const message = ref('Hello Vue!');
const greet = () => {
alert(message.value);
};
const test = computed(() => {
return meaages
})
watch(() => message, (newValue: any, oldValue: any) => {
})
9,mixins和hooks
Vue 2 中,Mixins 是一种全局特性,可以在多个组件之间共享代码。你可以创建一个 Mixin 对象,然后在组件中通过 Mixins 选项引入这个对象,从而将 Mixin 中的属性和方法合并到组件中;如果多个 Mixins 中有相同的属性或方法,可能会导致命名冲突。另外,由于 Mixins 是全局的,它们会增加组件的耦合度,使得代码难以维护
Vue3的Hooks允许你将相关的逻辑组合到一起,形成一个逻辑单元,组件内部使用的,而不是全局的,这减少了命名冲突和耦合度。
import { ref } from 'vue'
export default function() {
const count = ref(0);
const add = () => {
count.value++;
}
const decrement = () => {
count.value--;
}
// 把方法和数据返回出去
return {
count,
add,
decrement
}
}
// 在用到的文件中引入此hook.js 文件
<script setup>
//引入hooks文件
import useCount from "../hooks/useCount"
// 导入
const { count, add, decrement } = useCount()
</script>
二,插件
Vue3中,部分相关插件的用法也存在差异
1,vue-router
基本类似,在使用时需要引入; route和router, router获取路由器实例 ; route
对象包含了当前路由的各种信息
const router = useRouter() // 此为引入router
router.push({path:'name'})
router.back();
const route = useRoute() // 此为引入route
console.log(route.params)
console.log(route.query)
2,状态管理Vuex 和 Pinia
Vuex使用 store
、state
、mutations
、actions
和 getters
的概念,结构化更严格
Pinia 更简洁和模块化,使用 defineStore
函数创建状态,避免了冗长的代码结构
详细使用步骤见大佬文章:http://t.csdnimg.cn/mgdAX
import { defineStore } from 'pinia'
const useTimingInfoStore = defineStore('timingApproval', {
state: () => ({
testOne: 1,
testTwo: 2,
}),
actions: {
addSchemeData(data){
this.testOne= data
},
addTimingData(data){
this.testTwo= data
}
},
getters: {
doubleCount: (state) => state.testOne* 2
}
})
export default useTimingInfoStore
// 组件中使用
import { useTimingInfoStore} from '@/stores/timingApproval';
const timingInfoStore = useTimingInfoStore()
console.log(timingInfoStore.testOne)
timingInfoStore.addSchemeData(2)
码字不易,望各位未来大牛点赞支持一波~