分享 15 个 Vue3 全家桶开发的避坑经验
ztj100 2025-01-05 01:03 19 浏览 0 评论
最近入门 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 生命周期方法的变化蛮大的,先看看:
目前 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 个全局编译器宏,还没有详细介绍,这一节介绍 defineProps和 withDefaults。 使用 defineProps宏可以用来定义组件的入参,使用如下:
<script setup lang="ts">
let props = defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>();
</script>
这里只定义props属性中的 schema和 modelValue两个属性的类型, 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-bind 和 v-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.ts 的 resolve.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.ts 的 css.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.$router或 this.$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 修改数据状态的方式
按照官网给的方案,目前有三种方式修改:
- 通过 store.属性名赋值修改单笔数据的状态;
这个方法就是前面一节使用的:
const changeName = () => {
componentStoreObj.name = 'hello pingan8787';
}
- 通过 $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';
})
}
- 通过 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 模式确实越写越香。 本文内容如果有问题,欢迎大家一起评论讨论.
- 上一篇:解锁vue3全家桶和ts正确姿势
- 下一篇:Vue3 的改变
相关推荐
- Vue 技术栈(全家桶)(vue technology)
-
Vue技术栈(全家桶)尚硅谷前端研究院第1章:Vue核心Vue简介官网英文官网:https://vuejs.org/中文官网:https://cn.vuejs.org/...
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
-
前言《vue基础》系列是再次回炉vue记的笔记,除了官网那部分知识点外,还会加入自己的一些理解。(里面会有部分和官网相同的文案,有经验的同学择感兴趣的阅读)在开发时,是不是遇到过这样的场景,响应...
- vue3 组件初始化流程(vue组件初始化顺序)
-
学习完成响应式系统后,咋们来看看vue3组件的初始化流程既然是看vue组件的初始化流程,咋们先来创建基本的代码,跑跑流程(在app.vue中写入以下内容,来跑流程)...
- vue3优雅的设置element-plus的table自动滚动到底部
-
场景我是需要在table最后添加一行数据,然后把滚动条滚动到最后。查网上的解决方案都是读取html结构,暴力的去获取,虽能解决问题,但是不喜欢这种打补丁的解决方案,我想着官方应该有相关的定义,于是就去...
- Vue3为什么推荐使用ref而不是reactive
-
为什么推荐使用ref而不是reactivereactive本身具有很大局限性导致使用过程需要额外注意,如果忽视这些问题将对开发造成不小的麻烦;ref更像是vue2时代optionapi的data的替...
- 9、echarts 在 vue 中怎么引用?(必会)
-
首先我们初始化一个vue项目,执行vueinitwebpackechart,接着我们进入初始化的项目下。安装echarts,npminstallecharts-S//或...
- 无所不能,将 Vue 渲染到嵌入式液晶屏
-
该文章转载自公众号@前端时刻,https://mp.weixin.qq.com/s/WDHW36zhfNFVFVv4jO2vrA前言...
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
-
此篇幅比较长,涉及到的小知识点也比较多,一定要耐心看完,记住学东西没有耐心可不行!!!一、添加和修改注:添加和编辑用到了同一个组件,也就是此篇文章你能学会如何封装组件及引用组件;第二能学会async和...
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
-
前言本文整理了...
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
-
今天给大家分享的是Vue3聊天实例中的朋友圈的实现及登录验证和倒计时操作。先上效果图这个是最新开发的vue3.x网页端聊天项目中的朋友圈模块。用到了ElementPlus...
- 不来看看这些 VUE 的生命周期钩子函数?| 原力计划
-
作者|huangfuyk责编|王晓曼出品|CSDN博客VUE的生命周期钩子函数:就是指在一个组件从创建到销毁的过程自动执行的函数,包含组件的变化。可以分为:创建、挂载、更新、销毁四个模块...
- Vue3.5正式上线,父传子props用法更丝滑简洁
-
前言Vue3.5在2024-09-03正式上线,目前在Vue官网显最新版本已经是Vue3.5,其中主要包含了几个小改动,我留意到日常最常用的改动就是props了,肯定是用Vue3的人必用的,所以针对性...
- Vue 3 生命周期完整指南(vue生命周期及使用)
-
Vue2和Vue3中的生命周期钩子的工作方式非常相似,我们仍然可以访问相同的钩子,也希望将它们能用于相同的场景。...
- 救命!这 10 个 Vue3 技巧藏太深了!性能翻倍 + 摸鱼神器全揭秘
-
前端打工人集合!是不是经常遇到这些崩溃瞬间:Vue3项目越写越卡,组件通信像走迷宫,复杂逻辑写得脑壳疼?别慌!作为在一线摸爬滚打多年的老前端,今天直接甩出10个超实用的Vue3实战技巧,手把...
- 怎么在 vue 中使用 form 清除校验状态?
-
在Vue中使用表单验证时,经常需要清除表单的校验状态。下面我将介绍一些方法来清除表单的校验状态。1.使用this.$refs...
你 发表评论:
欢迎- 一周热门
- 最近发表
-
- Vue 技术栈(全家桶)(vue technology)
- vue 基础- nextTick 的使用场景(vue的nexttick这个方法有什么用)
- vue3 组件初始化流程(vue组件初始化顺序)
- vue3优雅的设置element-plus的table自动滚动到底部
- Vue3为什么推荐使用ref而不是reactive
- 9、echarts 在 vue 中怎么引用?(必会)
- 无所不能,将 Vue 渲染到嵌入式液晶屏
- vue-element-admin 增删改查(五)(vue-element-admin怎么用)
- 最全的 Vue 面试题+详解答案(vue面试题知识点大全)
- 基于 vue3.0 桌面端朋友圈/登录验证+60s倒计时
- 标签列表
-
- idea eval reset (50)
- vue dispatch (70)
- update canceled (42)
- order by asc (53)
- spring gateway (67)
- 简单代码编程 贪吃蛇 (40)
- transforms.resize (33)
- redisson trylock (35)
- 卸载node (35)
- np.reshape (33)
- torch.arange (34)
- node卸载 (33)
- npm 源 (35)
- vue3 deep (35)
- win10 ssh (35)
- exceptionininitializererror (33)
- vue foreach (34)
- idea设置编码为utf8 (35)
- vue 数组添加元素 (34)
- std find (34)
- tablefield注解用途 (35)
- python str转json (34)
- java websocket客户端 (34)
- tensor.view (34)
- java jackson (34)