前言
都2022年,vue3都更新到v3.2了,不会还有人不知道vue3的语法变化吧?刚好最近做过一个vue3的项目,这里记录下vue3的变化和实战中的使用。
vue3底层优化
vue3底层优化有兴趣可以去看官网,这里简单描述不做详情展开
tree-shaking
vue3底层使用了tree-shaking,将无用的代码打标记,压缩打包的时候不打包进去,减少了代码体积
对ts的支持
vue3使用ts进行重写,对ts支持很好,仅需加上lang="ts"即可使用
<script lang="ts">
</scipt>
数据劫持优化
使用proxy替代Object.defineProperty,不用再去初始化的时候遍历递归对象,proxy是在get的时候才去递归,所以性能上会更好,而且也不存在vue2初始值为空导致无法响应式的问题。
Vue实例变化
mian.js变化
vue2中:
可以看到是通过new Vue
去创建vue实例,通过Vue.use
去挂载使用插件。
//main.js
import Vue from "vue";
import App from "./App.vue";
import router from "./router";
import store from "./store";
import ElementUI from "element-ui";
import "element-ui/lib/theme-chalk/index.css";
Vue.use(ElementUI);
new Vue({
router,
store,
render: (h) => h(App),
}).$mount("#app");
vue3中:
vue3中会暴露胡一个createApp
的方法,通过createApp(App)
创建应用实例对象,某种意义上,createApp(App)和vue2的Vue
实例是差不多的
import { createApp } from 'vue';
import ElementPlus from 'element-plus';
import 'element-plus/dist/index.css';
import App from './App.vue';
import router from './permission';
import store from './store/index.js';
const app = createApp(App);
//如果全局注册组件:和vue2类似
app.component('name',Component)
app.use(ElementPlus).use(router).use(store).mount('#app');
vue-router变化
vue2中:
import Router from 'vue-router'
const router = new Router({
mode: 'history',
routes:[]
});
vue3中:
vue-router中会暴露胡一个createRouter
的方法,hash和history的对应方法如下:
import { createRouter, createWebHashHistory,createWebHashHistory} from 'vue-router';
const router = createRouter({
history: createWebHashHistory(), // hash模式:createWebHashHistory,
// history模式: createWebHashHistory
routes
});
Template变化
多个根标签
template中可以多个根标签,取消了vue2里面必须只能有一个根标签的限制
<template>
<div>123213</div>
<div>456</div>
</template>
v-model绑定多个值
vue3中合并了v-model和.sync,统一都使用v-model,多个值就是v-model:label,v-model:value类似于这样定义,修改父组件的值和.sync一致,使用emit('update:label',xxx)
<template>
<test-com v-model:label="xxx" v-model:value="xxx"/>
</template>
v-model默认值变化从value->modelValue
<test-com v-model="value"/>
vue2中:
props:{
value:{
type:String
}
}
vue3中:
props:{
modelValue:{
type:String
}
}
template标签v-for必须给key
vue2中:
<template v-for="item in [1,2,3]">
<div :key="item"></div
</template>
vue3中:
<template v-for="item in [1,2,3]" :key="item">
<div></div
</template>
v-for和v-if优先级调整
vue2中v-if和v-for不能一起使用,原因是v-for优先级大于v-if,造成性能浪费
vue3中更改了这个优先级,v-if的优先级大于v-for
vue2中:不通过
<template v-for="item in [1,2,3]" v-if="item>1">
<div :key="item"></div
</template>
vue3中:
<template v-for="item in [1,2,3]" :key="item" v-if="xxx">
<div></div
</template>
v-bind调整
vue2中v-bind是外面的优先级大于v-bind里面的
vue3中v-bind看位置,谁在后面谁覆盖前面的
vue3中v-bind和$listeners合并了,写v-bind即可
vue2中:
<test-com color="red" v-bind="{color:'blue'}"/> //color:red
vue3中:
<test-com color="red" v-bind="{color:'blue'}"/> //color:blue
<test-com v-bind="{color:'blue'}" color="red" /> //color:red
api语法变化
结构变化
打破了vue2的固定结构,采用setup标签包裹,取消this
vue2结构
vue3结构
vue3推荐结构
基础结构
<script>
setup(props,context){
// props:这里可以拿到props的值
//context:里面包括:{attrs,emits,slots}三个属性 context.attrs
//定义值:
const data=ref(0)
//定义方法
const addCount=()=>{
data.value++
}
//必须return出去,template才能拿到
return{
data,
addCount
}
}
</scirpt>
生命周期变化
语法变化
注:vue3中的ref,reactive,unRef,onMounted等等api都需要从vue中引入才可使用,不过后面我们通过配置vite来解决这个问题
声明值变化
定义基础数据类型:ref
<template>
{{data}} //template不需要加.value,vue3自动帮我们处理了
</template>
<script>
setup(){
const data=ref(0)
//修改这个值:
data.value=3 //必须加.value
console.log(data.value)
return{
data
}
}
</script>
是否是ref类型:isRef
<script>
setup(){
const data=ref(0)
const data2=0
isRef(data) //true
isRef(data2) //false
}
</script>
取值:unref语法糖
<script>
setup(){
const data=ref(0)
unref(data) //0 等价于isRef(data)?data.value:data
}
</script>
ref实例:需ref名字和定义ref的返回值一致
<template>
<div ref="testRef"></div>
</template>
<script>
setup(){
const testRef=ref(null)
//必须return出去才能对应上
return{
testRef
}
}
</script>
定义复杂数据类型:reactive
<template>
<div>{{state.data}}</div>
</template>
<script>
setup(){
const state=reactive({
data:1
})
return{
state
}
}
</script>
将对象的某个值转换成响应式类型:toRef
<template>
<div>{{data}}</div>
</template>
<script>
setup(){
const state=reactive({
data:1
})
//错误示例:这样会失去响应式
const {data}=state
//将state的data变成响应式数据
const data=toRef(state,data)
return{
data
}
}
</script>
将对象的所有值都变成响应式:toRefs
<template>
<div>{{data}},{{age}}</div>
</template>
<script>
setup(){
const state=reactive({
data:1,
age:1
})
return{
...toRefs(state)
}
}
</script>
computed
<template>
<div>{{data}}</div>
</template>
<script>
setup(){
const data=computed(()=>{
return 123
})
return{
data
}
}
</script>
watch
<script>
setup(props){
watch(
() => props.data,
(nl,ol) => {
state.tableData =nl ;
}, { deep: true, immediate: true });
}
</script>
获取组件实例
//这个proxy某种程度上==this
const { proxy } = getCurrentInstance();
//例如:我在app全局挂载了一个方法$test
页面中使用:proxy.$test()
setup语法糖
由于使用setup函数的方式有许多诟病,比如所有值必须return等问题,所以vue3.2新出了一个语法糖,使vue操作更简便
基础结构
<script setup>
const data=ref(0)
</script>
setup好处
- 定义的变量无需reutrn
- 引入组件无需注册
- 等等。。。
<template>
{{data}}
<test-com />
</template>
<script setup>
import testCom from "./testCom"
const data=ref(0)
</script>
props传值:defineProps
由于setup语法糖中没有props参数了,所以出现了defineProps方法去定义props
<template>
{{value}} //template中不需要写成props.value,vue帮我们处理了
</template>
<script setup>
const props=defineProps({
value:{
type:String
}
})
//使用:props.value
</script>
emit:defineEmits
vue3中所有emits方法都需要预先定义,才能正常使用,setup语法糖中使用defineEmits方法去定义emit
<script setup>
const emits = defineEmits(['changeA','changeB']);
const countAdd=()=>{
emits('changeA','11')
}
const countSum=()=>{
emits('changeB','22')
}
</script>
slot:useSlots
由于setup语法糖中没有context参数,所以获取slot插槽需要借助useSlots
只有在js逻辑里面需要使用slots才用到useSlots,如果直接template中写是不需要下面这一步的
<tempate>
<slot name="expend"></slot>
{{slots.xxxx}}
</template>
<script setup>
const slots = useSlots();
console.log(slots.expend)
</script>
attrs:useAttrs
由于setup语法糖中没有context参数,所以获取setup中获取attrs需要借助useSlots
只有在js逻辑里面需要使用attrs才用到useAttrs,如果直接template中写是不需要下面这一步的
<tempate>
<slot name="expend"></slot>
</template>
<script setup>
const slots = useSlots();
console.log(slots.expend)
</script>
defineExpose
由于setup语法糖中天然所有的属性方法都是封闭的,如果想从外部调用内部的方法,必须使用该api将变量方法暴露出去
<tempate>
<test-com ref="testComRef"/>
</template>
<script setup>
import testCom from "./testCom"
onMounted(()=>{
console.log(unref(testComRef).data)
})
</script>
//test-com.vue
<tempate>
</template>
<script setup>
const data=ref(1) //默认这样上面是拿不到的,因为没有暴露出去
const count=()=>{
return 111
}
defineExpose({
data, //这样才能拿到
count //方法也是一样,只要是想被外部拿到的,都需要暴露出去
})
</script>
vue3中写vue2的区别
相同点
vue3中写vue2目前基本做到完全兼容,你可以在vue3中还是按照vue2的方法去写,和写vue2是一样的
不同点
-
data必须是一个函数(vue2中data可以是函数可以是对象,不过是对象的话可能会存在复用问题)
-
mixin浅拷贝(有则跳过,无则拷贝)
vue2中:
<template>
</template>
<script>
import testMixin from "./testMixin"
export default{
mixins: [testMixin],
data(){
return{
value:{ //合并后: value:{a:1,b:2},aa:1
a:1
}
}
}
}
</script>
//testMixin.js
export default {
data(){
value:{
b:2
},
aa:1
}
}
vue3中(vue3中使用vue2):
<template>
</template>
<script>
import testMixin from "./testMixin"
export default{
mixins: [testMixin],
data(){
return{
value:{ //合并后: value:{a:1},aa:1
a:1
}
}
}
}
</script>
//testMixin.js
export default {
data(){
return{
value:{
b:2
},
aa:1
}
}
}
vue3移除的api(写setup的的时候无法使用的api)
-
filter (推荐computed或函数)
-
mixin(推荐使用hook,但是你还是可以写vue2时使用)
hook
mixin缺点:
-
难以追溯源
//假如以下场景
export default{
mixins: [minxin1,minxin2,minxin3,minxin4,minxin5,minxin6,minxin7,minxin8],
methods:{
testMethod(){
this.add() //我们很难知道这个add是哪个混入的方法
}
}
}
-
命名冲突
//假如以下场景
export default{
mixins: [minxin1,minxin2,minxin3,minxin4,minxin5,minxin6,minxin7,minxin8],
methods:{
testMethod(){
this.add() //每次创建minxin的时候还需要去其他minxin文件查看对应方法,防止命名冲突
}
}
}
hook的好处
-
容易追溯源
<script setup>
import useCount from "./hooks/useCount"
const {count,addCount}=useCount() //我们就可以很清楚知道这个useCount从哪个文件来的
</script>
-
重命名
<script setup>
import useCount from "./hooks/useCount"
//假如我这个页面已经有了addCount方法
const {count,addCount:otherCount}=useCount() //我们可以重命名(和ES6的解构重命名一样的玩法)
const addCount=()=>{
}
</script>
-
hook内部有状态
在hook中,你可以使用onMounted,watch,ref等许多api
<template>
{{count}} <button @click="addCount(1)">点击</button>
//点击后+1
</template>
<script setup>
import {useCount} from "./useCount"
const {count,addCount}=useCount() //这里拿到的count是具有响应式的
</script>
//useCount.js
export const useCount=()=>{
const count=ref(0)
const addCount=(value)=>{
count.value+=value
}
return{
count,
addCount
}
}
实现一个hook
命名规则:一般以useXXX开头
//useHook.js
//主体结构
export const useHook=()=>{
//定义变量
const data=ref(1)
const state=reactive({
a:1,
b:2
})
//定义方法 就跟写方法一样,也可以定义入参等
const addData=(value)=>{
data.value+=value
}
//定义的方法,变量需要被页面上使用的需要return出去
return {
data,
addData
}
}
//index.vue
<script setup>
//1.引入
import {useHook} from "./useHook"
//2.使用:
第一种办法: const {data,addData}=useHook()
第二种办法: const useHook=useHook() useHook.data useHook.addData
</script>
hook实战
假设一个页面有下拉框数据,下拉的数据都需要从接口获取,假设这个数据都需要初始化的时候获取:
以前的办法:
mounted(){
this.getA()
},
methods:{
getA(){
axios.get('xxx',res=>{
this.AList=res
})
},
}
使用hook:
export useOption=(url)=>{
const option=ref([])
onMounted(()=>{
getOption()
})
const getOption=()=>{
axios.get(url,res=>{
option.value=res
})
}
return{
option
}
}
//index.vue
<script setup>
import {useOption} from "./useOption"
//就这样就完事了
const {option:AList}=useOption('Aaa')
//甚至说多个,不过加一个别名即可
const{option:BList}=useOption('Bbb')
</script>
其他
hook可以根据实际场景进行抽离,这里推荐一下一个vue的hook工具插件: @vueuse/core
他这个里面有很多好用的hook,有兴趣可以去看一下。
全家桶变化
模块打包Vite(对标webpack)
在
vue2
中,基本上都是使用webpack
作为模块打包工具,但是webpack
的诟病就是页面过多热更新比较慢等等等等。 所以Vite出现了,强烈推荐使用Vite。
vite的优点:
- 将文件处理后以
es module
的形式按需给浏览器直接加载,提升了性能 - 依赖部分使用
esbuild
进行预构建,esbuild使用Go进行编写,比js快许多倍 - 冷启动,启动速度快
- 热替换,只更新对应esmodule文件,而且还是使用http304缓存
vite的不足:
- 目前生态可能没webpack好
使用后的感受:
快,很快,非常快!而且对vue3的支持很好,有很多插件对于vue3开发提升了很大的效率。
使用vite开发vue3:
都知道,vue3的ref,reactive必须要引入后才能使用,但是每个页面引入就很冗余,所以vite有插件来帮我们处理这个
-
安装插件:
npm i unplugin-auto-import -D
-
使用:
//vite.config.ts
import AutoImport from 'unplugin-auto-import/vite';
export default defineConfig({
plugins: [
AutoImport({
resolvers: [ElementPlusResolver()],
imports: ['vue', 'vue-router', 'vuex'], // 自动导库相关函数,不用每个文件都单独import
eslintrc: {
enabled: false, // 是否生成eslint忽略文件
filepath: './.eslintrc-auto-import.json',
globalsPropValue: true
}
}),
],
})
这样即可自动导入了,无需手动引入
Element Plus按需自动加载以及自动导入组件
-
安装插件:
npm i unplugin-vue-components -D
-
使用:
//vite.config.ts
import Components from 'unplugin-vue-components/vite';
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers';
export default defineConfig({
plugins: [
Components({
resolvers: [ElementPlusResolver()]
}),
],
})
这样即可实现element-plus按需自动加载以及components目录下的组件自动加载,无需引入
例如:
// components/base-text.vue
<template>
测试测试
</template>
//views/index.vue
<template>
<base-text/> //直接使用,不需要引入以及注册
</template>
状态管理Pinia(对标vuex)
pinia也是一个状态管理工具,他对于vue3和ts的支持非常好,而且非常容易上手,十分简单。
Pinia的特别之处
- 去除了mutations
- 对ts的支持很好
- 不再需要注入、导入函数、调用函数、享受自动完成功能!
- 无需动态添加 Store,默认情况下它们都是动态的,您甚至都不会注意到。请注意,您仍然可以随时手动使用 Store 进行注册,但因为它是自动的,您无需担心。
- 没有 命名空间模块。鉴于 Store 的扁平架构,“命名空间” Store 是其定义方式所固有的,您可以说所有 Store 都是命名空间的。
定义仓库:
//store/counter.js
import { defineStore } from 'pinia'
//defineStore第一个参数需要唯一的。
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
},
actions: {
increment() {
this.count++
},
},
})
页面上使用:
import { useStore } from '@/stores/counter'
export default {
setup() {
//这个store就包含count,increment
const store = useStore()
console.log(store.count)
console.log(store.increment)
return {
store,
}
},
}
部分Api
-
获取state:
1.第一种办法:
const pageCount=computed(()=>{
return store.count
})
2.第二种办法:
使用 const {count}=storeToRefs(store)
而不是:下面这样会失去响应式
const {count}=store
storeToRefs
的作用就是将state的值变成响应式,类似于reactive
-
$reset:重置仓库状态
const store = useStore()
store.$reset()
-
getter
页面上使用和使用state同理,这里就不展开了。
//store/counter.js
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', {
state: () => {
return {
count: 0
}
},
getters: {
doubleCount(state) {
return state.counter * 2
},
},
//或者:
getters: {
doubleCount() {
return this.counter * 2
},
},
actions: {
increment() {
this.counter++
},
},
})
-
Actions
1.第一种办法:
//直接操作值
直接store.counter=5
2.第二种办法: 定义actions,如上图,直接调用对应的方法名字即可。