共计 10915 个字符,预计需要花费 28 分钟才能阅读完成。
最近入门 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 模式的确越写越香。
本文内容如果有问题,欢送大家一起评论探讨。