最近入门 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 个 全局编译器宏 ,还没有具体介绍,这一节介绍 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 模式的确越写越香。
本文内容如果有问题,欢送大家一起评论探讨。