最近入门 Vue3 并实现 3 个我的项目,遇到问题蛮多的,明天就花点工夫整顿一下,和大家分享 15 个比拟常见的问题,根本都贴出对应文档地址,还请多看文档~
曾经实现的 3 个我的项目根本都是应用 Vue3 (setup-script 模式)全家桶开发,因而次要分几个方面总结:

  • Vue3
  • Vite
  • VueRouter
  • Pinia
  • ElementPlus

更多文章,欢送关注我的主页。

一、Vue3

1. Vue2.x 和 Vue3.x 生命周期办法的变动

文档地址:https://v3.cn.vuejs.org/guide/composition-api-lifecycle-hooks.html

Vue2.x 和 Vue3.x 生命周期办法的变动蛮大的,先看看:

2.x 生命周期3.x 生命周期执行工夫阐明
beforeCreatesetup组件创立前执行
createdsetup组件创立后执行
beforeMountonBeforeMount组件挂载到节点上之前执行
mountedonMounted组件挂载实现后执行
beforeUpdateonBeforeUpdate组件更新之前执行
updatedonUpdated组件更新实现之后执行
beforeDestroyonBeforeUnmount组件卸载之前执行
destroyedonUnmounted组件卸载实现后执行
errorCapturedonErrorCaptured当捕捉一个来自子孙组件的异样时激活钩子函数

目前 Vue3.x 仍然反对 Vue2.x 的生命周期,但不倡议混搭应用,后期能够先应用 2.x 的生命周期,前面尽量应用 3.x 的生命周期开发。

因为我应用都是 script-srtup模式,所以都是间接应用 Vue3.x 的生命周期函数:

// A.vue<script setup lang="ts">import { ref, onMounted } from "vue";let count = ref<number>(0);onMounted(() => {  count.value = 1;})</script>

每个钩子的执行机会点,也能够看看文档:
https://v3.cn.vuejs.org/guide/instance.html#生命周期图示

2. script-setup 模式中父组件获取子组件的数据

文档地址:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineexpose

这里次要介绍父组件如何去获取子组件外部定义的变量,对于父子组件通信,能够看文档介绍比拟具体:
https://v3.cn.vuejs.org/guide/component-basics.html

咱们能够应用全局编译器宏defineExpose宏,将子组件中须要裸露给父组件获取的参数,通过 {key: vlaue}形式作为参数即可,父组件通过模版 ref 形式获取子组件实例,就能获取到对应值:

// 子组件<script setup>    let name = ref("pingan8787")    defineExpose({ name }); // 显式裸露的数据,父组件才能够获取</script>// 父组件<Chlid ref="child"></Chlid><script setup>    let child = ref(null)    child.value.name //获取子组件中 name 的值为 pingan8787</script>

留神

  • 全局编译器宏只能在 script-setup 模式下应用;
  • script-setup 模式下,应用宏时无需 import能够间接应用;
  • script-setup 模式一共提供了 4 个宏,包含:defineProps、defineEmits、defineExpose、withDefaults。

3. 为 props 提供默认值

definedProps 文档:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemits
withDefaults 文档:https://v3.cn.vuejs.org/api/sfc-script-setup.html#%E4%BB%85%E9%99%90-typescript-%E7%9A%84%E5%8A%9F%E8%83%BD

后面介绍 script-setup 模式提供的 4 个全局编译器宏,还没有具体介绍,这一节介绍 definePropswithDefaults
应用 defineProps宏能够用来定义组件的入参,应用如下:

<script setup lang="ts">let props = defineProps<{    schema: AttrsValueObject;    modelValue: any;}>();</script>

这里只定义props属性中的 schemamodelValue两个属性的类型, defineProps 的这种申明的不足之处在于,它没有提供设置 props 默认值的形式。
其实咱们能够通过 withDefaults 这个宏来实现:

<script setup lang="ts">let props = withDefaults(  defineProps<{    schema: AttrsValueObject;    modelValue: any;  }>(),  {    schema: [],    modelValue: ''  });</script>
withDefaults 辅助函数提供了对默认值的类型查看,并确保返回的 props 的类型删除了已申明默认值的属性的可选标记。

4. 配置全局自定义参数

文档地址:https://v3.cn.vuejs.org/guide/migration/global-api.html#vue-prototype-%E6%9B%BF%E6%8D%A2%E4%B8%BA-config-globalproperties

在 Vue2.x 中咱们能够通过 Vue.prototype 增加全局属性 property。然而在 Vue3.x 中须要将 Vue.prototype 替换为 config.globalProperties 配置:

// Vue2.xVue.prototype.$api = axios;Vue.prototype.$eventBus = eventBus;// Vue3.xconst app = createApp({})app.config.globalProperties.$api = axios;app.config.globalProperties.$eventBus = eventBus;

应用时须要先通过 vue 提供的 getCurrentInstance办法获取实例对象:

// A.vue<script setup lang="ts">import { ref, onMounted, getCurrentInstance } from "vue";onMounted(() => {  const instance = <any>getCurrentInstance();  const { $api, $eventBus } = instance.appContext.config.globalProperties;  // do something})</script>

其中 instance内容输入如下:

5. v-model 变动

文档地址:https://v3.cn.vuejs.org/guide/migration/v-model.html

当咱们在应用 v-model指令的时候,实际上 v-bindv-on 组合的简写,Vue2.x 和 Vue3.x 又存在差别。

  • Vue2.x
<ChildComponent v-model="pageTitle" /><!-- 是以下的简写: --><ChildComponent :value="pageTitle" @input="pageTitle = $event" />

在子组件中,如果要对某一个属性进行双向数据绑定,只有通过 this.$emit('update:myPropName', newValue) 就能更新其 v-model绑定的值。

  • Vue3.x
<ChildComponent v-model="pageTitle" /><!-- 是以下的简写: --><ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event"/>

script-setup模式下就不能应用 this.$emit去派发更新事件,毕竟没有 this,这时候须要应用后面有介绍到的 defineProps、defineEmits 两个宏来实现:

// 子组件 child.vue// 文档:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemits<script setup lang="ts">import { ref, onMounted, watch } from "vue";const emit = defineEmits(['update:modelValue']); // 定义须要派发的事件名称let curValue = ref('');let props = withDefaults(defineProps<{    modelValue: string;}>(), {    modelValue: '',})onMounted(() => {   // 先将 v-model 传入的 modelValue 保留  curValue.value = props.modelValue;})watch(curValue, (newVal, oldVal) => {  // 当 curValue 变动,则通过 emit 派发更新  emit('update:modelValue', newVal)})</script><template>    <div></div></template><style lang="scss" scoped></style>

父组件应用的时候就很简略:

// 父组件 father.vue<script setup lang="ts">import { ref, onMounted, watch } from "vue";let curValue = ref('');  watch(curValue, (newVal, oldVal) => {  console.log('[curValue 发生变化]', newVal)})</script><template>    <Child v-model='curValue'></Child></template><style lang="scss" scoped></style>

6. 开发环境报错不好排查

文档地址:https://v3.cn.vuejs.org/api/application-config.html#errorhandler

Vue3.x 对于一些开发过程中的异样,做了更敌对的提醒正告,比方上面这个提醒:

这样可能更分明的告知异样的出处,能够看出大略是 <ElInput 0=......这边的问题,但还不够分明。
这时候就能够增加 Vue3.x 提供的全局异样处理器,更清晰的输入谬误内容和调用栈信息,代码如下

// main.tsapp.config.errorHandler = (err, vm, info) => {    console.log('[全局异样]', err, vm, info)}

这时候就能看到输入内容如下:

一下子就分明很多。
当然,该配置项也能够用来集成谬误追踪服务 Sentry 和 Bugsnag。
举荐浏览:Vue3 如何实现全局异样解决?

7. 察看 ref 的数据不直观,不不便

当咱们在控制台输入 ref申明的变量时。

const count = ref<numer>(0);console.log('[测试 ref]', count)

会看到控制台输入了一个 RefImpl对象:

看起来很不直观。咱们都晓得,要获取和批改 ref申明的变量的值,须要通过 .value来获取,所以你也能够:

console.log('[测试 ref]', count.value);

这里还有另一种形式,就是在控制台的设置面板中开启 「Enable custom formatters」选项。

这时候你会发现,控制台输入的 ref的格局发生变化了:


更加清晰直观了。

这个办法是我在《Vue.js 设计与实现》中发现的,但在文档也没有找到相干介绍,如果有敌人发现了,欢送告知~

二、Vite

1. Vite 动静导入的应用问题

文档地址:https://cn.vitejs.dev/guide/features.html#glob-import

应用 webpack 的同学应该都晓得,在 webpack 中能够通过 require.context动静导入文件:

// https://webpack.js.org/guides/dependency-management/require.context('./test', false, /\.test\.js$/);

在 Vite 中,咱们能够应用这两个办法来动静导入文件:

  • import.meta.glob

该办法匹配到的文件默认是懒加载,通过动静导入实现,构建时会拆散独立的 chunk,是异步导入,返回的是 Promise,须要做异步操作,应用形式如下:

const Components = import.meta.glob('../components/**/*.vue');// 转译后:const Components = {  './components/a.vue': () => import('./components/a.vue'),  './components/b.vue': () => import('./components/b.vue')}
  • import.meta.globEager

该办法是间接导入所有模块,并且是同步导入,返回后果间接通过 for...in循环就能够操作,应用形式如下:

const Components = import.meta.globEager('../components/**/*.vue');// 转译后:import * as __glob__0_0 from './components/a.vue'import * as __glob__0_1 from './components/b.vue'const modules = {  './components/a.vue': __glob__0_0,  './components/b.vue': __glob__0_1}

如果仅仅应用异步导入 Vue3 组件,也能够间接应用 Vue3 defineAsyncComponent API 来加载:

// https://v3.cn.vuejs.org/api/global-api.html#defineasynccomponentimport { defineAsyncComponent } from 'vue'const AsyncComp = defineAsyncComponent(() =>  import('./components/AsyncComponent.vue'))app.component('async-component', AsyncComp)

2. Vite 配置 alias 类型别名

文档地址:https://cn.vitejs.dev/config/#resolve-alias

当我的项目比较复杂的时候,常常须要配置 alias 门路别名来简化一些代码:

import Home from '@/views/Home.vue'

在 Vite 中配置也很简略,只须要在 vite.config.tsresolve.alias中配置即可:

// vite.config.tsexport default defineConfig({  base: './',  resolve: {    alias: {      "@": path.join(__dirname, "./src")    },  }  // 省略其余配置})

如果应用的是 TypeScript 时,编辑器会提醒门路不存在的正告⚠️,这时候能够在 tsconfig.json中增加 compilerOptions.paths的配置:

{  "compilerOptions": {    "paths": {      "@/*": ["./src/*"]     }  }}

3. Vite 配置全局 scss

文档地址:https://cn.vitejs.dev/config/#css-preprocessoroptions

当咱们须要应用 scss 配置的主题变量(如 $primary)、mixin办法(如 @mixin lines)等时,如:

<script setup lang="ts"></script><template>  <div class="container"></div></template><style scoped lang="scss">  .container{    color: $primary;    @include lines;  }</style>

咱们能够将 scss 主题配置文件,配置在 vite.config.tscss.preprocessorOptions.scss.additionalData中:

// vite.config.tsexport default defineConfig({  base: './',  css: {    preprocessorOptions: {      // 增加公共款式      scss: {        additionalData: '@import "./src/style/style.scss";'      }    }  },  plugins: [vue()]  // 省略其余配置})

如果不想应用 scss 配置文件,也能够间接写成 scss 代码:

export default defineConfig({  css: {    preprocessorOptions: {      scss: {        additionalData: '$primary: #993300'      }    }  }})

三、VueRouter

1. script-setup 模式下获取路由参数

文档地址:https://router.vuejs.org/zh/guide/advanced/composition-api.html

因为在 script-setup模式下,没有 this能够应用,就不能间接通过 this.$routerthis.$route来获取路由参数和跳转路由。
当咱们须要获取路由参数时,就能够应用 vue-router提供的 useRoute办法来获取,应用如下:

// A.vue<script setup lang="ts">import { ref, onMounted } from 'vue';import router from "@/router";import { useRoute } from 'vue-router'let detailId = ref<string>('');onMounted(() => {    const route = useRoute();    detailId.value = route.params.id as string; // 获取参数})</script>

如果要做路由跳转,就能够应用 useRouter办法的返回值去跳转:

const router = useRouter();router.push({  name: 'search',  query: {/**/},})

四、Pinia

1. store 解构的变量批改后没有更新

文档地址:https://pinia.vuejs.org/core-concepts/#using-the-store

当咱们解构出 store 的变量后,再批改 store 上该变量的值,视图没有更新:

// A.vue<script setup lang="ts">import componentStore from "@/store/component";const componentStoreObj = componentStore();  let { name } = componentStoreObj;  const changeName = () => {  componentStoreObj.name = 'hello pingan8787';}</script><template>  <span @click="changeName">{{name}}</span></template>

这时候点击按钮触发 changeName事件后,视图上的 name 并没有变动。这是因为 store 是个 reactive 对象,当进行解构后,会毁坏它的响应性。所以咱们不能间接进行解构。
这种状况就能够应用 Pinia 提供 storeToRefs工具办法,应用起来也很简略,只须要将须要解构的对象通过 storeToRefs办法包裹,其余逻辑不变:

// A.vue<script setup lang="ts">import componentStore from "@/store/component";import { storeToRefs } from 'pinia';const componentStoreObj = componentStore();  let { name } = storeToRefs(componentStoreObj); // 应用 storeToRefs 包裹  const changeName = () => {  componentStoreObj.name = 'hello pingan8787';}</script><template>  <span @click="changeName">{{name}}</span></template>

这样再批改其值,变更马上更新视图了。

2. Pinia 批改数据状态的形式

依照官网给的计划,目前有三种形式批改:

  1. 通过 store.属性名赋值批改单笔数据的状态;

这个办法就是后面一节应用的:

const changeName = () => {  componentStoreObj.name = 'hello pingan8787';}
  1. 通过 $patch办法批改多笔数据的状态;
文档地址:https://pinia.vuejs.org/api/interfaces/pinia._StoreWithState.html#patch

当咱们须要同时批改多笔数据的状态时,如果还是依照下面办法,可能要这么写:

const changeName = () => {  componentStoreObj.name = 'hello pingan8787'  componentStoreObj.age = '18'  componentStoreObj.addr = 'xiamen'}

下面这么写也没什么问题,然而 Pinia 官网曾经阐明,应用 $patch的效率会更高,性能更好,所以在批改多笔数据时,更举荐应用 $patch,应用形式也很简略:

const changeName = () => {  // 参数类型1:对象  componentStoreObj.$patch({    name: 'hello pingan8787',    age: '18',    addr: 'xiamen',  })    // 参数类型2:办法,该办法接管 store 中的 state 作为参数  componentStoreObj.$patch(state => {    state.name = 'hello pingan8787';    state.age = '18';    state.addr = 'xiamen';  })}
  1. 通过 action办法批改多笔数据的状态;

也能够在 store 中定义 actions 的一个办法来更新:

// store.tsimport { defineStore } from 'pinia';export default defineStore({    id: 'testStore',    state: () => {        return {            name: 'pingan8787',            age: '10',            addr: 'fujian'        }    },    actions: {        updateState(){            this.name = 'hello pingan8787';            this.age = '18';            this.addr = 'xiamen';        }    }})

应用时:

const changeName = () => {  componentStoreObj.updateState();}

这三种形式都能更新 Pinia 中 store 的数据状态。

五、Element Plus

1. element-plus 打包时 @charset 正告

我的项目新装置的 element-plus 在开发阶段都是失常,没有提醒任何正告,然而在打包过程中,控制台输入上面正告内容:

在官网 issues 中查阅很久:https://github.com/element-plus/element-plus/issues/3219。

尝试在 vite.config.ts中配置 charset: false,后果也是有效:

// vite.config.tsexport default defineConfig({  css: {    preprocessorOptions: {      scss: {        charset: false // 有效      }    }  }})

最初在官网的 issues 中找到解决办法:

// vite.config.ts// https://blog.csdn.net/u010059669/article/details/121808645css: {  postcss: {    plugins: [      // 移除打包element时的@charset正告      {        postcssPlugin: 'internal:charset-removal',        AtRule: {          charset: (atRule) => {            if (atRule.name === 'charset') {              atRule.remove();            }          }        }      }    ],  },}

2. 中文语言包配置

文档地址:https://element-plus.gitee.io/zh-CN/guide/i18n.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE

默认 elemnt-plus 的组件是英文状态:

咱们能够通过引入中文语言包,并增加到 ElementPlus 配置中来切换成中文:

// main.ts// ... 省略其余import ElementPlus from 'element-plus';import 'element-plus/dist/index.css';import locale from 'element-plus/lib/locale/lang/zh-cn'; // element-plus 中文语言包app.use(ElementPlus, { locale }); // 配置中文语言包

这时候就能看到 ElementPlus 外面组件的文本变成中文了。

总结

以上是我最近从入门到实战 Vue3 全家桶的 3 个我的项目后总结避坑教训,其实很多都是文档中有介绍的,只是刚开始不相熟。也心愿大伙多看看文档咯~
Vue3 script-setup 模式的确越写越香。
本文内容如果有问题,欢送大家一起评论探讨。