Recently, I started Vue3 and completed 3 projects. I encountered a lot of problems. I will take some time to sort it out today and share 15 more common problems with you. Basically, the corresponding document addresses are posted. Please read more documents~
The three completed projects are basically developed using Vue3 (setup-script mode), so they are mainly summarized in several aspects:
- vue3
- Vite
- VueRouter
- Pinia
- ElementPlus
For more articles, please pay attention to my homepage.
1. Vue3
1. Changes in Vue2.x and Vue3.x lifecycle methods
Document address: https://v3.cn.vuejs.org/guide/composition-api-lifecycle-hooks.html
The life cycle methods of Vue2.x and Vue3.x have changed a lot. Let’s take a look first:
2.x Lifecycle | 3.x Lifecycle | Execution time description |
---|---|---|
beforeCreate | setup | Execute before component creation |
created | setup | Execute after the component is created |
beforeMount | onBeforeMount | Executed before the component is mounted on the node |
mounted | onMounted | Execute after the component is mounted |
beforeUpdate | onBeforeUpdate | Execute before component update |
updated | onUpdated | Executed after the component update is complete |
beforeDestroy | onBeforeUnmount | Execute before the component is uninstalled |
destroyed | onUnmounted | Execute after component uninstallation is complete |
errorCaptured | onErrorCaptured | Activates the hook function when catching an exception from a descendant component |
At present, Vue3.x still supports the life cycle of Vue2.x, but it is not recommended to mix and match. You can use the life cycle of 2.x in the early stage, and try to use the life cycle of 3.x later.
Since I use the script-srtup
mode, I use the life cycle functions of Vue3.x directly:
// A.vue
<script setup lang="ts">
import { ref, onMounted } from "vue";
let count = ref<number>(0);
onMounted(() => {
count.value = 1;
})
</script>
The execution timing of each hook, you can also look at the documentation:
https://v3.cn.vuejs.org/guide/instance.html#Life cycle diagram
2. In the script-setup mode, the parent component obtains the data of the child component
Document address: https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineexpose
Here we mainly introduce how the parent component obtains the variables defined inside the child component. For the communication between parent and child components, you can refer to the documentation for more details:
https://v3.cn.vuejs.org/guide/component-basics.html
We can use the defineExpose
macro of the global compiler macro to expose the parameters in the subcomponent to the parent component, and pass the {key: vlaue}
method as a parameter, and the parent component can use the template ref method Get the subcomponent instance, you can get the corresponding value:
// 子组件
<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>
Note :
- Global compiler macros can only be used in script-setup mode;
- In script-setup mode, you don't need to use macros
import
you can use them directly; - The script-setup mode provides a total of 4 macros, including: defineProps , defineEmits , defineExpose , withDefaults .
3. Provide default values for props
definedProps documentation: https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemits
withDefaults documentation: 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
The four global compiler macros provided by script-setup mode have been introduced earlier, but they have not been introduced in detail. This section introduces defineProps
and withDefaults
.
Use the defineProps
macro to define the input parameters of the component, use the following:
<script setup lang="ts">
let props = defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>();
</script>
Here only define props
in the attribute schema
and modelValue
the type of the two attributes, the deficiency of defineProps
, which does not provide a way to set default values for props.
In fact, we can achieve this through the withDefaults macro:
<script setup lang="ts">
let props = withDefaults(
defineProps<{
schema: AttrsValueObject;
modelValue: any;
}>(),
{
schema: [],
modelValue: ''
}
);
</script>
The withDefaults helper function provides type checking for default values and ensures that the type of the returned props removes optional flags for properties that have declared default values.
4. Configure global custom parameters
Document address: https://v3.cn.vuejs.org/guide/migration/global-api.html#vue-prototype-%E6%9B%BF%E6%8D%A2%E4%B8%BA-config- globalproperties
In Vue2.x we can add the global property property by Vue.prototype
. But in Vue3.x you need to replace Vue.prototype
with config.globalProperties
configuration:
// 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;
When using, you need to first obtain the instance object through the getCurrentInstance
method provided by vue:
// 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>
Among them instance
The content output is as follows:
5. v-model changes
Document address: https://v3.cn.vuejs.org/guide/migration/v-model.html
When we are using the v-model
instruction, in fact, there are differences between v-bind
and v-on
combination of shorthand, Vue2.x and Vue3.x.
- Vue2.x
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :value="pageTitle" @input="pageTitle = $event" />
In the subcomponent, if you want to perform two-way data binding on a property, you only need to update the value of its v-model
binding through this.$emit('update:myPropName', newValue)
.
- Vue3.x
<ChildComponent v-model="pageTitle" />
<!-- 是以下的简写: -->
<ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event"/>
script-setup
cannot be used in mode this.$emit
to dispatch update events, after all, there is no this
, at this time, you need to use the defineProps and defineEmits described in the previous two macros. accomplish:
// 子组件 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>
When using the parent component, it is very simple:
// 父组件 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. Development environment errors are not easy to troubleshoot
Document address: https://v3.cn.vuejs.org/api/application-config.html#errorhandler
Vue3.x makes more friendly prompt warnings for some exceptions in the development process, such as the following prompt:
In this way, the source of the exception can be more clearly informed. It can be seen that the problem here is probably <ElInput 0=......
, but it is not clear enough.
At this time, you can add the global exception handler provided by Vue3.x to output the error content and call stack information more clearly. The code is as follows :
// main.ts
app.config.errorHandler = (err, vm, info) => {
console.log('[全局异常]', err, vm, info)
}
At this point, you can see the output as follows:
It became clearer at once.
Of course, this configuration item can also be used to integrate bug tracking services Sentry and Bugsnag .
Recommended reading: How does Vue3 implement global exception handling?
7. Observing the data of ref is not intuitive and inconvenient
When we output in the console ref
the declared variable.
const count = ref<numer>(0);
console.log('[测试 ref]', count)
You will see that the console outputs a RefImpl
object:
It seems very unintuitive. We all know that to obtain and modify the value of the variable declared by ref
039ddf5ed678af1b2b216defae138b2d---, we need to obtain it through .value
, so you can also:
console.log('[测试 ref]', count.value);
There is another way here, which is to turn on the " Enable custom formatters " option in the settings panel of the console.
At this time, you will find that the format of the console output ref
has changed:
More clear and intuitive.
I found this method in "Vue.js Design and Implementation", but I didn't find any relevant introduction in the document. If a friend finds it, please let me know~
2. Vite
1. The use of Vite dynamic import
Document address: https://cn.vitejs.dev/guide/features.html#glob-import
Students who use webpack should know that in webpack, you can dynamically import files through require.context
:
// https://webpack.js.org/guides/dependency-management/
require.context('./test', false, /\.test\.js$/);
In Vite, we can import files dynamically using these two methods:
-
import.meta.glob
The file matched by this method is lazy loaded by default, which is implemented by dynamic import . The independent chunk will be separated during construction, which is asynchronous import , and the return is Promise. It needs to do asynchronous operation. The usage is as follows:
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
This method is to directly import all modules , and it is a synchronous import , and the returned result can be operated directly through the for...in
loop. The usage is as follows:
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
}
If you only import Vue3 components asynchronously, you can also use the Vue3 defineAsyncComponent API to load them directly:
// 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 configure alias type alias
Document address: https://cn.vitejs.dev/config/#resolve-alias
When the project is more complex, it is often necessary to configure alias path aliases to simplify some code:
import Home from '@/views/Home.vue'
The configuration in Vite is also very simple, you only need to configure it in vite.config.ts
of resolve.alias
:
// vite.config.ts
export default defineConfig({
base: './',
resolve: {
alias: {
"@": path.join(__dirname, "./src")
},
}
// 省略其他配置
})
If you are using TypeScript, the editor will prompt a warning that the path does not exist⚠️, you can add the configuration of tsconfig.json
in compilerOptions.paths
:
{
"compilerOptions": {
"paths": {
"@/*": ["./src/*"]
}
}
}
3. Vite configure global scss
Document address: https://cn.vitejs.dev/config/#css-preprocessoroptions
When we need to use scss-configured theme variables (such as $primary
), mixin methods (such as @mixin lines
), etc., such as:
<script setup lang="ts">
</script>
<template>
<div class="container"></div>
</template>
<style scoped lang="scss">
.container{
color: $primary;
@include lines;
}
</style>
We can configure the scss theme configuration file in vite.config.ts
of css.preprocessorOptions.scss.additionalData
:
// vite.config.ts
export default defineConfig({
base: './',
css: {
preprocessorOptions: {
// 添加公共样式
scss: {
additionalData: '@import "./src/style/style.scss";'
}
}
},
plugins: [vue()]
// 省略其他配置
})
If you don't want to use the scss configuration file, you can also write the scss code directly:
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
additionalData: '$primary: #993300'
}
}
}
})
3. VueRouter
1. Get routing parameters in script-setup mode
Document address: https://router.vuejs.org/zh/guide/advanced/composition-api.html
Since in the script-setup
mode, there is no this
available, it cannot be used directly through this.$router
or this.$route
to get the routing parameters and routing parameters .
When we need to get the routing parameters, we can use the vue-router
useRoute
to get it, as follows:
// 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>
If you want to jump to the route, you can use the return value of the useRouter
method to jump:
const router = useRouter();
router.push({
name: 'search',
query: {/**/},
})
4. Pinia
1. The variables destructed by the store are not updated after modification
Document address: https://pinia.vuejs.org/core-concepts/#using-the-store
When we deconstruct the variable of the store and then modify the value of the variable on the store, the view is not updated:
// 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>
At this time, after clicking the button to trigger the changeName
event, the name
on the view does not change. This is because the store is a reactive object, and when destructed, it breaks its responsiveness. So we can't deconstruct directly.
In this case, you can use the storeToRefs
tool method provided by Pinia, which is also very simple to use. You only need to wrap the object to be deconstructed through the storeToRefs
method, and other logic remains unchanged:
// 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>
In this way, modify its value, and the change will update the view immediately.
2. How Pinia modifies the data state
According to the plan given by the official website, there are currently three ways to modify:
- Modify the status of a single data by
store.属性名
assignment;
This method is used in the previous section:
const changeName = () => {
componentStoreObj.name = 'hello pingan8787';
}
- Modify the status of multiple data through the
$patch
method;
Document address: https://pinia.vuejs.org/api/interfaces/pinia._StoreWithState.html#patch
When we need to modify the state of multiple pieces of data at the same time, if we still follow the above method, we may write:
const changeName = () => {
componentStoreObj.name = 'hello pingan8787'
componentStoreObj.age = '18'
componentStoreObj.addr = 'xiamen'
}
There is nothing wrong with the above, but the official website of Pinia has stated that using $patch
will have higher efficiency and better performance, so when modifying multiple data, it is more recommended to use $patch
, It is also very simple to use:
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';
})
}
- Modify the status of multiple data through the
action
method;
It is also possible to define a method of actions in the store to update:
// 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';
}
}
})
when using it:
const changeName = () => {
componentStoreObj.updateState();
}
All three methods can update the data state of the store in Pinia.
5. Element Plus
1. @charset warning when element-plus is packaged
The newly installed element-plus of the project is normal in the development stage, and no warnings are prompted, but during the packaging process, the console outputs the following warnings:
Check out the official issues for a long time: https://github.com/element-plus/element-plus/issues/3219 .
Try to configure ---9e07f115330b50c9ced4b0da9dbff3e9 vite.config.ts
in charset: false
, the result is also invalid:
// vite.config.ts
export default defineConfig({
css: {
preprocessorOptions: {
scss: {
charset: false // 无效
}
}
}
})
Finally found the solution in the official 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. Chinese language pack configuration
Document address: https://element-plus.gitee.io/zh-CN/guide/i18n.html#%E5%85%A8%E5%B1%80%E9%85%8D%E7%BD%AE
By default, the components of elemnt-plus are in English:
We can switch to Chinese by introducing the Chinese language pack and adding it to the ElementPlus configuration:
// 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 }); // 配置中文语言包
At this time, you can see that the text of the components in ElementPlus becomes Chinese.
Summarize
The above is the summary of my experience of avoiding pits after 3 projects from entry to actual combat Vue3 family bucket. In fact, many of them are introduced in the document, but I am not familiar with it at the beginning. I also hope that you will read more documents~
Vue3 script-setup mode is indeed more and more fragrant.
If you have any questions about the content of this article, please feel free to comment and discuss.
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。