一、前言
vue3已经发布一年了,今天和大家聊聊vue2和vue3。
在这之前,想问大家几个问题?大家可以带着这两个问题听我的分享
- 不言而喻vue3的诞生,肯定是为了解决vue2存在的问题,那么究竟解决了什么问题?
- vue3相比vue2性能上有哪些提升?
二、vue2和vue3的区别:
vue2基于Object.defineProperty实现,初始化数据在data中,业务逻辑处理放在(computed、methods、watch)以及各个生命周期函数中。
缺点:
- 无法监听到添加和删除的属性
- 无法监控到数组和对象的变化
- 业务逻辑分布在data、computed、methods、watch以及各个生命周期函数中,代码聚合性弱,不便于阅读
vue3基于Proxy实现,把所有的业务逻辑都放在setup函数中进行处理。
优点:
- 解决了vue2存在的问题
- 在diff算法上使用了静态标记的方式,大大提升了vue的执行效率
- 通过关注点分离可以解决单个文件代码冗余的问题(下面会专门讲这个问题)
- 静态提升
- 事件侦听器缓存
- 更好的支持TS
- 高内聚、低耦合
<script>
import { ref,defineComponent } from "vue";
export default defineComponent({
setup() {
const router = useRouter();
const serviceName = ref("");
let serviceData = ref<Array<IRecord>>([]);
//得到服务商的列表
const getServiceList = () => {
let data = {
name: serviceName?.value,
};
serviceList(data).then(res => {
if (res.code === 0) {
serviceData.value = res.data.list;
}
});
};
//添加
const addEvent = () => {
router.push({
path: "service-add"
});
};
//编辑
const editEvent = (record: IRecord & { id: number }) => {
router.push(`service-edit/${record.id}`);
};
//搜索
const searchEvent = () => {
getServiceList();
};
onMounted(() => {
//得到服务商的列表
getServiceList();
});
return {
serviceData,
serviceColumns,
serviceName,
//事件
searchEvent,
addEvent,
editEvent
};
}
})
</script>
复制代码
三、vue3新特性
组合式API(Composition API)
组合式API实就是一个setup函数,把相关的业务逻辑放在一起解决了之前vue2代码分散的问题,最后返回需要用的变量(响应式变量、非响应式变量)和函数。
import { ref,defineComponent } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
const handleClick = () => {
}
return {
count,
handleClick
}
}
})
复制代码
最简单的setup由定义的数据和函数组成,最终在return中返回,供模板中使用。接下来具体讲一下setup由哪些内容组成:
1、ref() 响应式函数
import { ref } from 'vue'
const count = ref(0)
复制代码
ref接受一个参数,返回的是一个带有value属性的对象,通过value属性可以修改对应的值。
import { ref,defineComponent } from 'vue'
export default defineComponent({
setup() {
const count = ref(0)
console.log(count.value); //0
//修改value的值
count.value++;
console.log(count.value); //1
}
})
复制代码
问题:
ref接受一个参数之后,为什么返回的是一个对象?
ref就是把传入的值和对象统一转化成Proxy对象,因为Proxy只支持引用对象,所以对于值对象会转化成{ value: "值"} 再转化成Proxy对象,此时就可以监听到value值的变化。
为什么要用ref?
相当vue2中data定义的数据,这样vue底层就知道了它是响应式变量。
2、reactive 返回对象的响应式副本
const person = reactive({ name: "天下" })
复制代码
返回的person是一个响应式对象,和原对象并不相等,因此建议只用proxy对象。
import { reactive,defineComponent } from 'vue'
export default defineComponent({
setup() {
const obj = {
name: "天下"
}
const person = reactive(obj)
console.log(obj === person) //false
//修改值
setTimeout(()=>{
person.name = "晓";
person.age = 18;
},1000)
}
})
复制代码
3、在setup如何用生命周期函数
组合式API和选项式AP声明周期函数基本一致,区别是组合式API加了前缀"on",有如下生命周期函数
Vue2 | Vue3 |
---|---|
beforeCreate | |
created | |
beforeMount | onBeforeMount |
mounted | onMounted |
beforeUpdate | onBeforeUpdate |
updated | onUpdated |
beforeMount | onBeforeUnmount |
unmounted | onUnmounted |
errorCaptured | onErrorCaptured |
renderTracked | onRenderTracked |
renderTriggered | onRenderTirggered |
activated | onActivated |
deactivated | onDeactivated |
import { onMounted,defineComponent } from "vue";
export default defineComponent({
setup() {
onMounted(()=>{
console.log("onMounted");
})
}
})
复制代码
4、watch 监听
接受三个参数:
- 想要侦听的响应式引用
- 一个回调
- 可选的配置选项
import { ref,watch,defineComponent } from "vue";
export default defineComponent({
setup() {
const num = ref(0);
watch(num,(newNum,oldNum)=>{
console.log("newNum",newNum); //666
console.log("oldNum",oldNum); //0
})
setTimeout(()=>{
num.value = 666;
},1000)
}
})
复制代码
5、computed
import { ref,computed,defineComponent } from "vue";
export default defineComponent({
setup() {
const str = ref("Hello");
const splitVal = computed(()=>{
return str.value.split("");
})
console.log("splitVal.value",splitVal.value); //['H', 'e', 'l', 'l', 'o']
}
})
复制代码
6、setup 函数
参数:
- props
- context
props 是响应式的,传入新的prop时会被更新。
//父组件
<test-setup title="北京欢迎你"></test-setup>
复制代码
//子组件
export default defineComponent({
props: {
title: String
},
setup(props,context) {
console.log("--- setup ---");
console.log("props",props); //Proxy {title: '北京欢迎你'}
console.log("context",context);
}
})
复制代码
注意:因为props是响应式的,因此不能用ES6解构,它会消除prop的响应式。
如果需要解构可以在setup函数中使用Refs。
//失去了响应式
const { message } = props;
console.log("message",message); //父组件信息
const { title } = toRefs(props);
console.log("title",title); //ObjectRefImpl {_object: Proxy, _key: 'title', __v_isRef: true}
复制代码
如果title是可选的prop,则传入的prop可能没有此属性,在这种情况下toRefs不会创建一个ref,需要用toRef代替。
import { toRef,defineComponent } from "vue";
export default defineComponent({
setup() {
const otherMessage = toRef(props,"otherMessage");
console.log("otherMessage",otherMessage.value); //undefined
}
})
复制代码
Context 是一个普通的js对象,它暴露了组件的3个属性
- attrs 属性
- slots 插槽
- emit 触发事件
context对象不是响应式对象,因此可以解构
import { defineComponent } from "vue";
export default defineComponent({
setup(props,{ attrs, slots, emit}) {
console.log("attrs",attrs);
console.log("slots",slots);
console.log("emit",emit);
}
})
复制代码
7、ref和reactive的区别:
ref和reactive都可以实现变量响应式,ref主要用于单个变量,reactive主要用于表单对象中多一些。
两者在使用上也有差别:ref之后的变量通过.value值的方式访问,reactive直接可以通过[data].[property]的方式访问。
四、关注点分离
<a-form-item class="width200">
<a-select
placeholder="请选择小程序"
v-model:value="formState.applet"
:options="appletData"
@change="appletHandle"
>
</a-select>
</a-form-item>
复制代码
import useAppletData from "./market-data-helper/useAppletData";
export default defineComponent({
setup() {
const { appletData } = useAppletData();
return {
appletData
}
}
})
复制代码
相关业务逻辑代码分离出去
下面useAppletData.ts文件的主要作用是把请求到的下拉数据分离出去,请求到数据之后return出去,然后在上面的列表页引入使用。
//useAppletData.ts
import { ref, onMounted } from "vue";
import { appletList } from "../../../../api/common";
interface IOptions {
label: string;
value: string;
}
const appletDataOptions = [{ label: "全部小程序", value: "" }];
export default function useAppletData() {
const appletData = ref<Array<IOptions>>(appletDataOptions);
const getAppletList = async () => {
let data = {
page: 1,
pageSize: 999999
};
let res = await appletList(data);
if (res.code === 0) {
appletData.value = res;
}
};
return {
appletData
};
}
复制代码
这样可以大大提高阅读的速度,代码也不会冗余。
五、在vue-router中的用法
在vue2中,我们通过this.$router这种方式进行路由跳转的,但是在vue3中没有this这一概念,因此我们是这样使用的:
import { defineComponent } from "vue";
import { useRouter } from "vue-router";
export default defineComponent({
const count = ref(0);
const router = useRouter();
const handleClick = () => {
router.push({ name: "AddApplet" });
}
return {
count,
handleClick
}
})
复制代码
六、在vuex中的用法
import { useStore } from 'vuex'
export default defineComponent({
setup() {
const store = useStore()
const count = store.state.count;
return {
count,
}
}
})
复制代码
七、对于TypeScript的支持
从列表最初数据的定义到请求接口再到返回数据,都进行了约束。
列表:
//列表定义的数据
interface Applet {
id?: number;
code: string;
createAt: String,
logo: string,
merchantName: string,
name: string;
serviceName: string;
tag: string;
tagId: string;
}
const appletData = ref<Array<Applet>>([]);
复制代码
//返回值 data中的值定义
interface AppletListResponse {
count: number;
list: Array<Applet>;
page: number | string;
pageSize: number | string;
}
//列表返回的值定义
interface AppletListRes {
code: number;
msg: string;
data: AppletListResponse;
}
appletList(params).then((res: AppletListRes) => {
if (res.code === 0) {
appletData.value = res.data.list;
total.value = res.data.count;
}
});
复制代码
//参数
interface AppletParams {
page: number | string;
pageSize: number | string;
name?: string;
storeName?: string;
serviceName?: string;
tag?: string;
}
//接口请求
export const appletList = (params: AppletParams): Promise<AppletListRes> => {
return Http.get("/applet/list", params);
};
复制代码
八、script setup语法糖
大家可能发现了,虽然我们可以进行关注点分离代码,但是setup函数里面的代码依旧看起来很多,那么有没有什么方法可以让setup变的更加简短一些呢?下面我就介绍一下vue3 3.2版本正式发布的script setup语法糖,可真是用起来特别甜。
1、setup基本用法:
使用起来特别简单,只需要在script标签上加上setup关键字即可。
<script setup></script>
复制代码
2、组件自动注册
在script setup中,引入的组件,无需在components注册。
<script setup>
import HelloWorld from './components/HelloWorld.vue'
</script>
<template>
<HelloWorld msg="Hello Vue 3 + Vite" />
</template>
复制代码
3、组件核心API的使用
(1)使用defineProps
通过 defineProps 指定当前props类型,获得上下文的props对象。
<script setup>
import { ref } from 'vue'
const props = defineProps({
msg: String
})
const count = ref(0)
</script>
复制代码
(2)使用emits
父组件:
<script setup>
import HelloWorld from './components/HelloWorld.vue'
const handle = (val) => {
console.log(val); //天下
}
</script>
<template>
<HelloWorld
msg="Hello Vue 3 + Vite"
@handle="handle"
/>
</template>
复制代码
<script setup>
import {
defineEmits
} from 'vue'
const emits = defineEmits("handle");
const handleClick = ()=> {
emits("handle","天下")
}
</script>
<template>
<button type="button" @click="handleClick">点击</button>
</template>
复制代码
(3)获取slots和attrs
通过useAttrs、useSlots分别获取slots和attrs。
<script setup>
import { defineProps,useAttrs,useSlots } from 'vue'
const attrs = useAttrs();
const slots = useSlots();
</script>
复制代码
(4)属性和方法无需返回,直接使用
之前,setup函数中的变量和函数都要return,才能在模板中使用,现在在script setup中定义无需return,直接可以使用,有没有感觉到意外的惊喜?
<script setup>
import { ref } from 'vue'
const count = ref(0)
const handleClick = ()=> count.value++
</script>
<template>
<button type="button" @click="handleClick">count is: {{ count }}</button>
</template>
复制代码
大家有没有觉得简单多了。
最后我为大家总结一下:
vue3不论在底层还是逻辑上都有很大的优势,采用Proxy代替了Object.defineProperty方法的用法,解决了vue之前在处理数组和对象的缺陷。通过优化diff算法、静态提升、事件侦听器缓存,极大的提升了vue的性能。
从使用层面,vue3使用组合式API,抛弃了选项式(data、methods、watch、mounted)隔离这种方式,组合式API讲究的是代码的高内聚低耦合。当代码过多的时候用了关注点分离的方式大大提高了代码的可读性。最后vue3最近还使用了script setup语法糖使得代码更加简洁精炼。