一起养成写作习惯!这是我参与「掘金日新计划 · 4 月更文挑战」的第1天,点击查看活动详情。
最近入门 Vue3 并完成 3 个项目,遇到问题蛮多的,今天就花点时间整理一下,和大家分享 15 个比较常见的问题,基本都贴出对应文档地址,还请多看文档~
已经完成的 3 个项目基本都是使用 Vue3 (setup-script 模式)全家桶开发,因此主要分几个方面总结:
- Vue3
- Vite
- VueRouter
- Pinia
- ElementPlus
一、Vue3
1. Vue2.x 和 Vue3.x 生命周期方法的变化
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>
每个钩子的执行时机点,也可以看看文档: v3.cn.vuejs.org/guide/insta…
2. script-setup 模式中父组件获取子组件的数据
这里主要介绍父组件如何去获取子组件内部定义的变量,关于父子组件通信,可以看文档介绍比较详细: v3.cn.vuejs.org/guide/compo…
我们可以使用全局编译器宏的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 文档:v3.cn.vuejs.org/api/sfc-scr… withDefaults 文档:v3.cn.vuejs.org/api/sfc-scr…
前面介绍 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. 配置全局自定义参数
在 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 变化
当我们在使用 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. 开发环境报错不好排查
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 设计与实现》中发现的,但在文档也没有找到相关介绍,如果有朋友发现了,欢迎告知~