likes
comments
collection
share

十三.2022年了,你还不会vue3吗?

作者站长头像
站长
· 阅读数 39

前言

都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结构

十三.2022年了,你还不会vue3吗?

vue3结构

十三.2022年了,你还不会vue3吗?

vue3推荐结构

十三.2022年了,你还不会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>

生命周期变化

十三.2022年了,你还不会vue3吗?

语法变化

注: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,如上图,直接调用对应的方法名字即可。

最后

以上是对vue3个人的一些理解,vue3目前已经比较稳定了,后面好像会针对ref必须使用.value做优化,期待下吧。