乐趣区

关于前端:分享-15-个-Vue3-全家桶开发的避坑经验

最近入门 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 生命周期 执行工夫阐明
beforeCreate setup 组件创立前执行
created setup 组件创立后执行
beforeMount onBeforeMount 组件挂载到节点上之前执行
mounted onMounted 组件挂载实现后执行
beforeUpdate onBeforeUpdate 组件更新之前执行
updated onUpdated 组件更新实现之后执行
beforeDestroy onBeforeUnmount 组件卸载之前执行
destroyed onUnmounted 组件卸载实现后执行
errorCaptured onErrorCaptured 当捕捉一个来自子孙组件的异样时激活钩子函数

目前 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.x
Vue.prototype.$api = axios;
Vue.prototype.$eventBus = eventBus;

// Vue3.x
const 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.ts
app.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#defineasynccomponent

import {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.ts
export 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.ts
export 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.ts
import {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.ts
export default defineConfig({
  css: {
    preprocessorOptions: {
      scss: {charset: false // 有效}
    }
  }
})

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

// vite.config.ts

// https://blog.csdn.net/u010059669/article/details/121808645
css: {
  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 模式的确越写越香。
本文内容如果有问题,欢送大家一起评论探讨。

退出移动版