15 个 Vue3 全家桶开发的避坑经验

2022-11-26 0 853

原副标题:15 个 Vue3 全家人桶合作开发的避坑实战经验

源自社会公众号: 后端三五

镜像:https://juejin.cn/post/7084536432731095048/

前段时间进阶 Vue3 并顺利完成 3 个工程项目,碰到难题蛮多的,那时就点儿天数重新整理呵呵,和我们撷取 15 个较为常用的难题,基本上都贴出相关联文件格式门牌号,还请多看文件格式~ 早已顺利完成的 3 个工程项目基本上都是采用 Vue3 (setup- 商业模式)全家人桶合作开发,因此主要就分两个各方面归纳:

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 Captured 当捕获一个源自子孙模块的异常时激活钩子函数

目前 Vue3.x 依然支持 Vue2.x 的生命周期,但不建议混搭采用,前期可以先采用 2.x 的合作开发周期,后面尽量采用 3.x 的合作开发周期合作开发。

由于我采用都是 -srtup商业模式,所以都是直接采用 Vue3.x 的合作开发周期函数:

// A.vue

< setup lang=”ts”>

import { ref, onMounted } from “vue”;

let count = ref<number>(0);

onMounted( => {

count.value = 1;

})

</>

每个钩子的继续执行时机点,也可以看一看文件格式:https://v3.cn.vuejs.org/guide/instance.html#合作开发周期图示

2. -setup 商业模式中父组

文件格式门牌号:https://v3.cn.vuejs.org/api/sfc–setup.html#defineexpose

件通信,可以看文件格式介绍较为详细:https://v3.cn.vuejs.org/guide/component-basics.html

我们可以采用 全局编译器宏defineExpose宏,将子模块中需{key: vlaue}

// 子模块

< setup>

let name = ref(“pingan8787”)

</>

// 父模块

<Chlid ref=”child”></Chlid>

< setup>

let child = ref(null)

</>

注意

全局编译器宏只能在 -setup 商业模式下采用;

-setup 商业模式下,采用宏时无需import可以直接采用;

-setup 商业模式一共提供了 4 个宏,包括:defineProps、defineEmits、defineExpose、withDefaults。

3. 为 props 提供默认值

definedProps 文件格式:https://v3.cn.vuejs.org/api/sfc–setup.html#defineprops-%E5%92%8C-defineemitswithDefaults 文件格式:https://v3.cn.vuejs.org/api/sfc–setup.html#%E4%BB%85%E9%99%90-type-%E7%9A%84%E5%8A%9F%E8%83%BD

前面介绍 -setup 商业模式提供的 4 个 全局编译器宏,还没有详细介绍,这一节介绍 definePropswithDefaults。采用 defineProps宏可以用来定义模块的入参,采用如下:

< setup lang=”ts”>

let props = defineProps<{

schema: AttrsValueObject;

modelValue: any;

}>;

</>

这里只定义props属性中的 schemamodelValue两个属性的类型, defineProps的这种声明的不足之处在于,它没有提供设置 props 默认值的方式。

其实我们可以通过 withDefaults 这个宏来实现:

< setup lang=”ts”>

let props = withDefaults(

defineProps<{

schema: AttrsValueObject;

modelValue: any;

}>,

{

schema: [],

modelValue:

}

);

</>

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

constapp = createApp({})

app.config.globalProperties.$api = axios;

app.config.globalProperties.$eventBus = eventBus;

采用时需要先通过 vue 提供的 getCurrentInstance

// A.vue

< setup lang=”ts”>

import { ref, onMounted, getCurrentInstance } from “vue”;

onMounted( => {

const instance = <any>getCurrentInstance;

const { $api, $eventBus } = instance.appContext.config.globalProperties;

// do something

})

</>

其中 instance内容输出如下:

15 个 Vue3 全家桶开发的避坑经验

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”/>

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

// 子模块 child.vue

// 文件格式:https://v3.cn.vuejs.org/api/sfc–setup.html#defineprops-%E5%92%8C-defineemits

< 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)

})

</>

<template>

<div></div>

</template>

<style lang=”scss” scoped></style>

父模块采用的时候就很简单:

// 父模块 father.vue

< setup lang=”ts”>

import { ref, onMounted, watch } from “vue”;

let curValue = ref();

watch(curValue, (newVal, oldVal) => {

console.log([curValue 发生变动], newVal)

})

</>

<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 对于一些合作开发过程中的异常,做了更友好的提示警告,比如下面这个提示:

15 个 Vue3 全家桶开发的避坑经验

这样能够更清楚的告知异常的出处,可以看出大概是 <ElInput 0=……这边的难题,但还不够清楚。这时候就可以添加 Vue3.x 提供的 全局异常处理器,更清晰的 输出错误内容和调用栈信息,代码如下

// main.ts

app.config.errorHandler =( err, vm, info) => {

console.log( [全局异常], err, vm, info)

}

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

呵呵子就清楚很多。当然,该配置项也可以用来集成错误追踪服务 Sentry 和 Bugsnag。推荐阅读:Vue3 如何实现全局异常处理?

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

当我们在控制台输出 ref声明的变量时。

constcount = ref<numer>(0);

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

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

ref声明的变量的值,需要通过 .value

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

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

image.png

image.png

这时候你会发现,控制台输出的 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,需要做异步操作,采用方式如下:

constComponents = import.meta.glob( ../components/**/*.vue);

// 转译后:

constComponents = {

./components/a.vue: => import( ./components/a.vue),

./components/b.vue: => import( ./components/b.vue)

}

import.meta.globEager

该方式是 直接导入所有模块,并且是 同步导入,返回结果直接通过 for…in循环就可以操作,采用方式如下:

constComponents = import.meta.globEager( ../components/**/*.vue);

// 转译后:

import* as__glob__0_0from./components/a.vue

import* as__glob__0_1 from./components/b.vue

constmodules = {

./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 }fromvue

constAsyncComp = defineAsyncComponent( =>

import( ./components/AsyncComponent.vue)

)

app.component(async-component, AsyncComp)

2. Vite 配置 alias 类型别名

文件格式门牌号:https://cn.vitejs.dev/config/#resolve-alias

当工程项目较为复杂的时候,经常需要配置 alias 路径别名来简化一些代码:

importHome from@/views/Home.vue

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

// vite.config.ts

exportdefaultdefineConfig({

base: ./,

resolve: {

alias: {

“@”: path.join(__dirname,“./src”)

},

}

// 省略其他配置

})

如果采用的是 Type 时,编辑器会提示路径不存在的警告⚠️,这时候可以在 tsconfig.json中添加 compilerOptions.paths的配置:

{

“compilerOptions”: {

“paths”: {

“@/*”: [ “./src/*”]

}

}

}

3. Vite 配置全局 scss

文件格式门牌号:https://cn.vitejs.dev/config/#css-preprocessoroptions

当我们需要采用 scss 配置的主题变量(如 $primary)、mixin方式(如 @mixin lines)等时,如:

< setup lang=”ts”>

</>

<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

exportdefaultdefineConfig({

base: ./,

css: {

preprocessorOptions: {

// 添加公共样式

scss: {

additionalData: @import “./src/style/style.scss”;

}

}

},

plugins: [vue]

// 省略其他配置

})

如果不想采用 scss 配置文件,也可以直接写成 scss 代码:

exportdefaultdefineConfig({

css: {

preprocessorOptions: {

scss: {

additionalData: $primary: #993300

}

}

}

})

文件格式门牌号:https://router.vuejs.org/zh/guide/advanced/composition-api.html

由于在 -setup商业模式下,没有 this可以采用,就不能直接通过 this.$routerthis.$routevue-router提供的 useRoute

// A.vue

< 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;

})

</>

如果要做路由跳转,就可以采用 useRouter方式的返回值去跳转:

constrouter = useRouter;

router.push({

name: search,

query: { /**/},

})

四、Pinia 1. store 解构的变量修改后没有更新

文件格式门牌号:https://pinia.vuejs.org/core-concepts/#using-the-store

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

// A.vue

< setup lang=”ts”>

import componentStore from “@/store/component”;

const componentStoreObj = componentStore;

let { name } = componentStoreObj;

const changeName = => {

componentStoreObj.name = hello pingan8787;

}

</>

<template>

<span @click=”changeName”>{{name}}</span>

</template>

这时候点击按钮触发 changeName事件后,视图上的 name并没有变动。这是因为 store 是个 reactive 对象,当进行解构后,会破坏它的响应性。所以我们不能直接进行解构。这种情况就可以采用 Pinia 提供storeToRefs工具方式,采用起来也很简单,只需要将需要解构的对象通过 storeToRefs方式包裹,其他逻辑不变:

// A.vue

< 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;

}

</>

<template>

<span @click=”changeName”>{{name}}</span>

</template>

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

2. Pinia 修改数据状态的方式

按照官网给的方案,目前有三种方式修改:

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

这个方式就是前面一节采用的:

constchangeName = => {

componentStoreObj.name = hello pingan8787;

}

通过 $patch方式修改多笔数据的状态;

文件格式门牌号:https://pinia.vuejs.org/api/interfaces/pinia._StoreWithState.html#patch

当我们需要同时修改多笔数据的状态时,如果还是按照上面方式,可能要这么写:

constchangeName = => {

componentStoreObj.name = hello pingan8787

componentStoreObj.age = 18

componentStoreObj.addr = xiamen

}

上面这么写也没什么难题,但是 Pinia 官网早已表明,采用 $patch的效率会更高,性能更好,所以在修改多笔数据时,更推荐采用 $patch,采用方式也很简单:

constchangeName = => {

// 参数类型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;

})

}

通过 action方式修改多笔数据的状态;

也可以在 store 中定义 actions 的一个方式来更新:

// store.ts

import{ defineStore } frompinia;

exportdefaultdefineStore({

id: testStore,

state: => {

return{

name:pingan8787,

age: 10,

addr: fujian

}

},

actions: {

updateState{

this.name = hello pingan8787;

this.age = 18;

this.addr =xiamen;

}

}

})

采用时:

constchangeName = => {

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

exportdefaultdefineConfig({

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

// … 省略其他

importElementPlusfromelement-plus;

importelement-plus/dist/index.css;

importlocale fromelement-plus/lib/locale/lang/zh-cn; // element-plus 中文语言包

app.use(ElementPlus, { locale }); // 配置中文语言包

这时候就能看到 ElementPlus 里面模块的文本变成中文了。

归纳

以上是我前段时间从进阶到实战 Vue3 全家人桶的 3 个工程项目后归纳避坑实战经验,其实很多都是文件格式中有介绍的,只是刚开始不熟悉。也希望大伙多看一看文件格式咯~

Vue3 -setup 商业模式确实越写越香。

本文内容如果有难题,欢迎我们一起评论讨论。

— EOF —

推荐↓↓↓

相关文章

发表评论
暂无评论
官方客服团队

为您解决烦忧 - 24小时在线 专业服务