123.jpg
最近入门 Vue3 并完成 3 个项目,遇到问题蛮多的,今天就花点时间整理一下,和大家分享 23 个比较常见的问题,基本都贴出对应文档地址,还请多看文档~
已经完成的 3 个项目基本都是使用 Vue3 (setup-script 模式)全家桶开发,因此主要分几个方面总结:

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 的生命周期函数:

  1. // A.vue
  2. <script setup lang="ts">
  3. import { ref, onMounted } from "vue";
  4. let count = ref<number>(0);
  5. onMounted(() => {
  6. count.value = 1;
  7. })
  8. </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 方式获取子组件实例,就能获取到对应值:

  1. // 子组件
  2. <script setup>
  3. let name = ref("pingan8787")
  4. defineExpose({ name }); // 显式暴露的数据,父组件才可以获取
  5. </script>
  6. // 父组件
  7. <Chlid ref="child"></Chlid>
  8. <script setup>
  9. let child = ref(null)
  10. child.value.name //获取子组件中 name 的值为 pingan8787
  11. </script>

注意

  • 全局编译器宏只能在 script-setup 模式下使用;
  • script-setup 模式下,使用宏时无需 import可以直接使用;
  • script-setup 模式一共提供了 4 个宏,包括:definePropsdefineEmitsdefineExposewithDefaults

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 个全局编译器宏,还没有详细介绍,这一节介绍 definePropswithDefaults
使用 defineProps宏可以用来定义组件的入参,使用如下:

  1. <script setup lang="ts">
  2. let props = defineProps<{
  3. schema: AttrsValueObject;
  4. modelValue: any;
  5. }>();
  6. </script>

这里只定义props属性中的 schemamodelValue两个属性的类型, defineProps 的这种声明的不足之处在于,它没有提供设置 props 默认值的方式。
其实我们可以通过 withDefaults 这个宏来实现:

  1. <script setup lang="ts">
  2. let props = withDefaults(
  3. defineProps<{
  4. schema: AttrsValueObject;
  5. modelValue: any;
  6. }>(),
  7. {
  8. schema: [],
  9. modelValue: ''
  10. }
  11. );
  12. </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 配置:

  1. // Vue2.x
  2. Vue.prototype.$api = axios;
  3. Vue.prototype.$eventBus = eventBus;
  4. // Vue3.x
  5. const app = createApp({})
  6. app.config.globalProperties.$api = axios;
  7. app.config.globalProperties.$eventBus = eventBus;

使用时需要先通过 vue 提供的 getCurrentInstance方法获取实例对象:

  1. // A.vue
  2. <script setup lang="ts">
  3. import { ref, onMounted, getCurrentInstance } from "vue";
  4. onMounted(() => {
  5. const instance = <any>getCurrentInstance();
  6. const { $api, $eventBus } = instance.appContext.config.globalProperties;
  7. // do something
  8. })
  9. </script>

其中 instance内容输出如下:
image.png

5. v-model 变化

文档地址:https://v3.cn.vuejs.org/guide/migration/v-model.html

当我们在使用 v-model指令的时候,实际上 v-bindv-on 组合的简写,Vue2.x 和 Vue3.x 又存在差异。

  • Vue2.x ```vue

  1. 在子组件中,如果要对某一个属性进行双向数据绑定,只要通过 `this.$emit('update:myPropName', newValue)` 就能更新其 `v-model`绑定的值。
  2. - Vue3.x
  3. ```vue
  4. <ChildComponent v-model="pageTitle" />
  5. <!-- 是以下的简写: -->
  6. <ChildComponent :modelValue="pageTitle" @update:modelValue="pageTitle = $event"/>

script-setup模式下就不能使用 this.$emit去派发更新事件,毕竟没有 this,这时候需要使用前面有介绍到的 definePropsdefineEmits 两个宏来实现:

  1. // 子组件 child.vue
  2. // 文档:https://v3.cn.vuejs.org/api/sfc-script-setup.html#defineprops-%E5%92%8C-defineemits
  3. <script setup lang="ts">
  4. import { ref, onMounted, watch } from "vue";
  5. const emit = defineEmits(['update:modelValue']); // 定义需要派发的事件名称
  6. let curValue = ref('');
  7. let props = withDefaults(defineProps<{
  8. modelValue: string;
  9. }>(), {
  10. modelValue: '',
  11. })
  12. onMounted(() => {
  13. // 先将 v-model 传入的 modelValue 保存
  14. curValue.value = props.modelValue;
  15. })
  16. watch(curValue, (newVal, oldVal) => {
  17. // 当 curValue 变化,则通过 emit 派发更新
  18. emit('update:modelValue', newVal)
  19. })
  20. </script>
  21. <template>
  22. <div></div>
  23. </template>
  24. <style lang="scss" scoped></style>

父组件使用的时候就很简单:

  1. // 父组件 father.vue
  2. <script setup lang="ts">
  3. import { ref, onMounted, watch } from "vue";
  4. let curValue = ref('');
  5. watch(curValue, (newVal, oldVal) => {
  6. console.log('[curValue 发生变化]', newVal)
  7. })
  8. </script>
  9. <template>
  10. <Child v-model='curValue'></Child>
  11. </template>
  12. <style lang="scss" scoped></style>

6. 开发环境报错不好排查

文档地址:https://v3.cn.vuejs.org/api/application-config.html#errorhandler

Vue3.x 对于一些开发过程中的异常,做了更友好的提示警告,比如下面这个提示:
image.png
这样能够更清楚的告知异常的出处,可以看出大概是 <ElInput 0=......这边的问题,但还不够清楚。
这时候就可以添加 Vue3.x 提供的全局异常处理器,更清晰的输出错误内容和调用栈信息,代码如下

  1. // main.ts
  2. app.config.errorHandler = (err, vm, info) => {
  3. console.log('[全局异常]', err, vm, info)
  4. }

这时候就能看到输出内容如下:
image.png
一下子就清楚很多。
当然,该配置项也可以用来集成错误追踪服务 SentryBugsnag
推荐阅读:Vue3 如何实现全局异常处理?

7. 观察 ref 的数据不直观,不方便

当我们在控制台输出 ref声明的变量时。

  1. const count = ref<numer>(0);
  2. console.log('[测试 ref]', count)

会看到控制台输出了一个 RefImpl对象:
image.png
看起来很不直观。我们都知道,要获取和修改 ref声明的变量的值,需要通过 .value来获取,所以你也可以:

  1. console.log('[测试 ref]', count.value);

这里还有另一种方式,就是在控制台的设置面板中开启 「Enable custom formatters」选项。
image.pngimage.png
这时候你会发现,控制台输出的 ref的格式发生变化了:
image.png
更加清晰直观了。

这个方法是我在《Vue.js 设计与实现》中发现的,但在文档也没有找到相关介绍,如果有朋友发现了,欢迎告知~

8. watch 数组不会收到更新

文档地址:https://v3.cn.vuejs.org/api/computed-watch-api.html#watcheffect

当我们在操作数组,并使用 watch做监听时,会发现数组变化后,watch的更新事件却只执行一次,后续数组更新不再执行,测试代码如下:

  1. // 1. 使用 reactive 定义数组
  2. let arr = reactive<any>([]);
  3. const change => {
  4. arr.push(...newVal);
  5. });
  6. watch(arr, (newVal, oldVal) => {
  7. console.log('更新数组')
  8. })
  9. // 2. 使用 ref 定义数组
  10. let arr = ref<any>([]);
  11. const change => {
  12. arr.value.push(...newVal);
  13. });
  14. watch(arr.value, (newVal, oldVal) => {
  15. console.log('更新数组')
  16. })

那么应该如何解决呢,我们以 reactive定义的数组为例来看下解决方法,把 watch第一个参数改成方法调用的形式:

  1. watch(() => [...arr], (newVal, oldVal) => {
  2. console.log('更新数组')
  3. })

二、Vite

1. Vite 动态导入的使用问题

文档地址:https://cn.vitejs.dev/guide/features.html#glob-import

使用 webpack 的同学应该都知道,在 webpack 中可以通过 require.context动态导入文件:

  1. // https://webpack.js.org/guides/dependency-management/
  2. require.context('./test', false, /\.test\.js$/);

在 Vite 中,我们可以使用这两个方法来动态导入文件:

  • import.meta.glob

该方法匹配到的文件默认是懒加载,通过动态导入实现,构建时会分离独立的 chunk,是异步导入,返回的是 Promise,需要做异步操作,使用方式如下:

  1. const Components = import.meta.glob('../components/**/*.vue');
  2. // 转译后:
  3. const Components = {
  4. './components/a.vue': () => import('./components/a.vue'),
  5. './components/b.vue': () => import('./components/b.vue')
  6. }
  • import.meta.globEager

该方法是直接导入所有模块,并且是同步导入,返回结果直接通过 for...in循环就可以操作,使用方式如下:

  1. const Components = import.meta.globEager('../components/**/*.vue');
  2. // 转译后:
  3. import * as __glob__0_0 from './components/a.vue'
  4. import * as __glob__0_1 from './components/b.vue'
  5. const modules = {
  6. './components/a.vue': __glob__0_0,
  7. './components/b.vue': __glob__0_1
  8. }

如果仅仅使用异步导入 Vue3 组件,也可以直接使用 Vue3 defineAsyncComponent API 来加载:

  1. // https://v3.cn.vuejs.org/api/global-api.html#defineasynccomponent
  2. import { defineAsyncComponent } from 'vue'
  3. const AsyncComp = defineAsyncComponent(() =>
  4. import('./components/AsyncComponent.vue')
  5. )
  6. app.component('async-component', AsyncComp)

2. Vite 配置 alias 类型别名

文档地址:https://cn.vitejs.dev/config/#resolve-alias

当项目比较复杂的时候,经常需要配置 alias 路径别名来简化一些代码:

  1. import Home from '@/views/Home.vue'

在 Vite 中配置也很简单,只需要在 vite.config.tsresolve.alias中配置即可:

  1. // vite.config.ts
  2. export default defineConfig({
  3. base: './',
  4. resolve: {
  5. alias: {
  6. "@": path.join(__dirname, "./src")
  7. },
  8. }
  9. // 省略其他配置
  10. })

如果使用的是 TypeScript 时,编辑器会提示路径不存在的警告⚠️,这时候可以在 tsconfig.json中添加 compilerOptions.paths的配置:

  1. {
  2. "compilerOptions": {
  3. "paths": {
  4. "@/*": ["./src/*"]
  5. }
  6. }
  7. }

3. Vite 配置全局 scss

文档地址:https://cn.vitejs.dev/config/#css-preprocessoroptions

当我们需要使用 scss 配置的主题变量(如 $primary)、mixin方法(如 @mixin lines)等时,如:

  1. <script setup lang="ts">
  2. </script>
  3. <template>
  4. <div class="container"></div>
  5. </template>
  6. <style scoped lang="scss">
  7. .container{
  8. color: $primary;
  9. @include lines;
  10. }
  11. </style>

我们可以将 scss 主题配置文件,配置在 vite.config.tscss.preprocessorOptions.scss.additionalData中:

  1. // vite.config.ts
  2. export default defineConfig({
  3. base: './',
  4. css: {
  5. preprocessorOptions: {
  6. // 添加公共样式
  7. scss: {
  8. additionalData: '@import "./src/style/style.scss";'
  9. }
  10. }
  11. },
  12. plugins: [vue()]
  13. // 省略其他配置
  14. })

如果不想使用 scss 配置文件,也可以直接写成 scss 代码:

  1. export default defineConfig({
  2. css: {
  3. preprocessorOptions: {
  4. scss: {
  5. additionalData: '$primary: #993300'
  6. }
  7. }
  8. }
  9. })

三、VueRouter

1. script-setup 模式下获取路由参数

文档地址:https://router.vuejs.org/zh/guide/advanced/composition-api.html

由于在 script-setup模式下,没有 this可以使用,就不能直接通过 this.$routerthis.$route来获取路由参数和跳转路由。
当我们需要获取路由参数时,就可以使用 vue-router提供的 useRoute方法来获取,使用如下:

  1. // A.vue
  2. <script setup lang="ts">
  3. import { ref, onMounted } from 'vue';
  4. import router from "@/router";
  5. import { useRoute } from 'vue-router'
  6. let detailId = ref<string>('');
  7. onMounted(() => {
  8. const route = useRoute();
  9. detailId.value = route.params.id as string; // 获取参数
  10. })
  11. </script>

如果要做路由跳转,就可以使用 useRouter方法的返回值去跳转:

  1. const router = useRouter();
  2. router.push({
  3. name: 'search',
  4. query: {/**/},
  5. })

2. VurRouter4.x 实现跳转 404 页面

文档地址:https://router.vuejs.org/zh/guide/migration/index.html#%E5%88%A0%E9%99%A4%E4%BA%86-routeroptions-%E4%B8%AD%E7%9A%84-fallback-%E5%B1%9E%E6%80%A7

在 VueRouter 4.x 之前,可以这么配置跳转 404 页面的路由:

  1. // router.ts
  2. // ...
  3. {
  4. path: '*',
  5. name: '404',
  6. component: () => import('@/page/404')
  7. }

但是这样在 VueRouter4.x 是不行的,因为在 VueRouter4.x 中,已经移除 path: '*'的写法,需要改成下面写法:

  1. // router.ts
  2. // ...
  3. {
  4. path: '/:pathMatch(.*)',
  5. name: '404',
  6. component: () => import('@/page/404')
  7. }

更多官方示例如下:

  1. const routes = [
  2. // pathMatch 是参数的名称,例如,跳转到 /not/found 会得到
  3. // { params: { pathMatch: ['not', 'found'] } }
  4. // 这要归功于最后一个 *,意思是重复的参数,如果你
  5. // 打算直接使用未匹配的路径名称导航到该路径,这是必要的
  6. { path: '/:pathMatch(.*)*', name: 'not-found', component: NotFound },
  7. // 如果你省略了最后的 `*`,在解析或跳转时,参数中的 `/` 字符将被编码
  8. { path: '/:pathMatch(.*)', name: 'bad-not-found', component: NotFound },
  9. ]
  10. // 如果使用命名路由,不好的例子:
  11. router.resolve({
  12. name: 'bad-not-found',
  13. params: { pathMatch: 'not/found' },
  14. }).href // '/not%2Ffound'
  15. // 好的例子:
  16. router.resolve({
  17. name: 'not-found',
  18. params: { pathMatch: ['not', 'found'] },
  19. }).href // '/not/found'

四、Pinia

1. store 解构的变量修改后没有更新

文档地址:https://pinia.vuejs.org/core-concepts/#using-the-store

当我们解构出 store 的变量后,再修改 store 上该变量的值,视图没有更新:

  1. // A.vue
  2. <script setup lang="ts">
  3. import componentStore from "@/store/component";
  4. const componentStoreObj = componentStore();
  5. let { name } = componentStoreObj;
  6. const changeName = () => {
  7. componentStoreObj.name = 'hello pingan8787';
  8. }
  9. </script>
  10. <template>
  11. <span @click="changeName">{{name}}</span>
  12. </template>

这时候点击按钮触发 changeName事件后,视图上的 name 并没有变化。这是因为 store 是个 reactive 对象,当进行解构后,会破坏它的响应性。所以我们不能直接进行解构。
这种情况就可以使用 Pinia 提供 storeToRefs工具方法,使用起来也很简单,只需要将需要解构的对象通过 storeToRefs方法包裹,其他逻辑不变:

  1. // A.vue
  2. <script setup lang="ts">
  3. import componentStore from "@/store/component";
  4. import { storeToRefs } from 'pinia';
  5. const componentStoreObj = componentStore();
  6. let { name } = storeToRefs(componentStoreObj); // 使用 storeToRefs 包裹
  7. const changeName = () => {
  8. componentStoreObj.name = 'hello pingan8787';
  9. }
  10. </script>
  11. <template>
  12. <span @click="changeName">{{name}}</span>
  13. </template>

这样再修改其值,变更马上更新视图了。

2. Pinia 修改数据状态的方式

按照官网给的方案,目前有三种方式修改:

  1. 通过 store.属性名赋值修改单笔数据的状态;

这个方法就是前面一节使用的:

  1. const changeName = () => {
  2. componentStoreObj.name = 'hello pingan8787';
  3. }
  1. 通过 $patch方法修改多笔数据的状态;

    文档地址:https://pinia.vuejs.org/api/interfaces/pinia._StoreWithState.html#patch

当我们需要同时修改多笔数据的状态时,如果还是按照上面方法,可能要这么写:

  1. const changeName = () => {
  2. componentStoreObj.name = 'hello pingan8787'
  3. componentStoreObj.age = '18'
  4. componentStoreObj.addr = 'xiamen'
  5. }

上面这么写也没什么问题,但是 Pinia 官网已经说明,使用 $patch的效率会更高,性能更好,所以在修改多笔数据时,更推荐使用 $patch,使用方式也很简单:

  1. const changeName = () => {
  2. // 参数类型1:对象
  3. componentStoreObj.$patch({
  4. name: 'hello pingan8787',
  5. age: '18',
  6. addr: 'xiamen',
  7. })
  8. // 参数类型2:方法,该方法接收 store 中的 state 作为参数
  9. componentStoreObj.$patch(state => {
  10. state.name = 'hello pingan8787';
  11. state.age = '18';
  12. state.addr = 'xiamen';
  13. })
  14. }
  1. 通过 action方法修改多笔数据的状态;

也可以在 store 中定义 actions 的一个方法来更新:

  1. // store.ts
  2. import { defineStore } from 'pinia';
  3. export default defineStore({
  4. id: 'testStore',
  5. state: () => {
  6. return {
  7. name: 'pingan8787',
  8. age: '10',
  9. addr: 'fujian'
  10. }
  11. },
  12. actions: {
  13. updateState(){
  14. this.name = 'hello pingan8787';
  15. this.age = '18';
  16. this.addr = 'xiamen';
  17. }
  18. }
  19. })

使用时:

  1. const changeName = () => {
  2. componentStoreObj.updateState();
  3. }

这三种方式都能更新 Pinia 中 store 的数据状态。

五、Element Plus

1. element-plus 打包时 @charset 警告

项目新安装的 element-plus 在开发阶段都是正常,没有提示任何警告,但是在打包过程中,控制台输出下面警告内容:
image.png
在官方 issues 中查阅很久:https://github.com/element-plus/element-plus/issues/3219

尝试在 vite.config.ts中配置 charset: false,结果也是无效:

  1. // vite.config.ts
  2. export default defineConfig({
  3. css: {
  4. preprocessorOptions: {
  5. scss: {
  6. charset: false // 无效
  7. }
  8. }
  9. }
  10. })

最后在官方的 issues 中找到处理方法:

  1. // vite.config.ts
  2. // https://blog.csdn.net/u010059669/article/details/121808645
  3. css: {
  4. postcss: {
  5. plugins: [
  6. // 移除打包element时的@charset警告
  7. {
  8. postcssPlugin: 'internal:charset-removal',
  9. AtRule: {
  10. charset: (atRule) => {
  11. if (atRule.name === 'charset') {
  12. atRule.remove();
  13. }
  14. }
  15. }
  16. }
  17. ],
  18. },
  19. }

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 的组件是英文状态:
image.png
我们可以通过引入中文语言包,并添加到 ElementPlus 配置中来切换成中文:

  1. // main.ts
  2. // ... 省略其他
  3. import ElementPlus from 'element-plus';
  4. import 'element-plus/dist/index.css';
  5. import locale from 'element-plus/lib/locale/lang/zh-cn'; // element-plus 中文语言包
  6. app.use(ElementPlus, { locale }); // 配置中文语言包

这时候就能看到 ElementPlus 里面组件的文本变成中文了。
image.png

3. TypeScript 类型报错

文档地址:https://www.typescriptlang.org/tsconfig#skipLibCheck

这个经验来自网友“吴一周”同学,报错内容如下:
image.png
这时候可以在 tsconfig.json 文件中增加 compilerOptions.skipLibCheck的配置:

  1. // tsconfig.json
  2. {
  3. "compilerOptions": {
  4. "skipLibCheck": true
  5. }
  6. }

4. 通过路由表动态配置 el-menu 名称和图标

文档地址:https://v3.cn.vuejs.org/guide/component-dynamic-async.html#%E5%8A%A8%E6%80%81%E7%BB%84%E4%BB%B6-%E5%BC%82%E6%AD%A5%E7%BB%84%E4%BB%B6

在做后台管理系统的时候,经常需要创建左侧菜单,比如:
image.png
在不考虑后台动态配置菜单列表的情况下,我们会在前端维护一个路由表,然后使用 Element-UI 中的 el-menu组件,再通过 v-for遍历出每一个 el-menu-item菜单项。
路由表如下:

  1. export const MenusConfig = [
  2. { label: '创建页面', icon: 'edit', route: '/admin/edit' },
  3. { label: '模版商城', icon: 'shop', route: '/admin/store' },
  4. { label: '页面列表', icon: 'list', route: '/admin/list' },
  5. ]
  6. <el-menu :default-active="currentMenuIndex">
  7. <template v-for="(menu, index) in MenusConfig" :key="index">
  8. <el-menu-item :index="index+''">
  9. <el-icon><icon-menu /></el-icon>
  10. <template #title>{{menu.label}}</template>
  11. </el-menu-item>
  12. </template>
  13. </el-menu>

这时候我们可以看到 Element-UI 文档示例中,都是直接使用 el-icon组件包裹实际图标的组件,比如:

  1. 文档地址:
  2. https://element-plus.gitee.io/zh-CN/component/menu.html#%E5%8F%AF%E6%8A%98%E5%8F%A0%E7%9A%84%E8%8F%9C%E5%8D%95
  3. <el-menu-item index="2">
  4. <el-icon><icon-menu /></el-icon>
  5. <template #title>Navigator Two</template>
  6. </el-menu-item>

这时候我们就没办法直接通过 v-for显示出图标,因为遍历出来的是字符串的图标名称。
其实这时候就可以用到 Vue3 提供的动态组件实现,实现如下:

  1. <el-menu :default-active="currentMenuIndex">
  2. <template v-for="(menu, index) in MenusConfig" :key="index">
  3. <el-menu-item :index="index+''">
  4. <el-icon><component :is="menu.icon"></component></el-icon>
  5. <template #title>{{menu.label}}</template>
  6. </el-menu-item>
  7. </template>
  8. </el-menu>

核心就是 <component :is="menu.icon"></component>,通过 is指定具体组件名称。

5. Table toggleRowSelection 不生效

文档地址:https://element-plus.gitee.io/zh-CN/component/table.html#%E5%A4%9A%E9%80%89 参考方案:https://cloud.tencent.com/developer/article/1917452

当使用 element-plus table 多选时,如果我们首次已经选择几笔数据后,再进入表格准备编修改已经选中的几项(比如增加、移除已选中的项等),这时如果我们按照文档使用的方式,将已选列表当做参数传入,则会出现表格中的项并不会变成选中状态的情况。

  1. const toggleSelection = (rows?: User[]) => {
  2. if (rows) {
  3. rows.forEach((row) => {
  4. multipleTableRef.value!.toggleRowSelection(row, undefined)
  5. })
  6. } else {
  7. multipleTableRef.value!.clearSelection()
  8. }
  9. }

image.png
这个问题文档里面也没有描述,解决方法如下:
multipleTableRef.value!.toggleRowSelection方法的第一个参数的数据源改成和 table 绑定的 data相同即可:

  1. const renderSelectedData = (rows?: []) => {
  2. if (rows) {
  3. nextTick(() => {
  4. multipleTableRef.value!.clearSelection();
  5. rows.forEach((row: ArticleType) => {
  6. if(curData.value.result){
  7. // fix: 已选择的数据无法回显到 ElementPlus Table 上
  8. multipleTableRef.value!.toggleRowSelection(
  9. curData.value.result.find((item: ArticleType) =>
  10. item.image === row.image && item.link === row.link
  11. )
  12. , true);
  13. }
  14. });
  15. });
  16. } else {
  17. multipleTableRef.value!.clearSelection();
  18. }
  19. };
  20. // 省略其他
  21. <el-table :data="curData.result" ref="multipleTableRef"
  22. @selection-change="selectArticle">
  23. </el-table>

如上代码:

  • curData.value.resultel-tabledata的数据源是同一份;
  • renderSelectedData方法中遍历 rows时,将 multipleTableRef.value!.toggleRowSelection 第一个参数的值设置为 curData中的值。

所以还是指针问题。

6. 无法覆盖 element-plus 样式

当我们需要自定义 element-plus 部分组件的样式时,通常会在 vue 文件下的 style直接修改对应组件样式,比如修改下拉选择框样式:

  1. <style lang="scss" scoped>
  2. .DataDesignerPanel {
  3. .values-tag {
  4. .el-input__wrapper {
  5. background: transparent!important;
  6. border: 0!important;
  7. box-shadow: 0 0px 0px!important;
  8. }
  9. }
  10. }
  11. </style>

可以发现并没有剩下,这时候,可以将 style标签中的 scoped属性去掉即可:

  1. <style lang="scss">
  2. .DataDesignerPanel {
  3. .values-tag {
  4. .el-input__wrapper {
  5. background: transparent!important;
  6. border: 0!important;
  7. box-shadow: 0 0px 0px!important;
  8. }
  9. }
  10. }
  11. </style>

六、vue-echarts

当重复生成图表时,控制台会提醒:

  1. There is a chart instance already initialized on the dom.

这时候可以先使用 echarts 提供的 dispose方法删除 DOM:

  1. const renderChart = () => {
  2. chart.clear && chart.clear();
  3. // 删除 DOM
  4. echarts.dispose(document.getElementById('BarChart') as HTMLElement)
  5. const chartDom = document.getElementById('BarChart')!;
  6. chart = echarts.init(chartDom);
  7. chart.setOption(chartData.value);
  8. }

七、vue-grid-layout

1. Vue3 使用问题

参考方案:https://github.com/jbaysolutions/vue-grid-layout/issues/637#issuecomment-1114368571

在 Vue3 项目中使用的时候,会提示下面错误信息:

  1. external_commonjs_vue_commonjs2_vue_root_Vue_default.a is not a constructor

这是因为使用默认 npm install vue-grid-layout安装是 Vue2 版本,在 Vue3 中需要这样安装:

  1. pnpm add vue-grid-layout@3.0.0-beta1

然后在 main.ts中引入:

  1. import { createApp } from 'vue'
  2. import App from './App.vue'
  3. import VueGridLayout from 'vue-grid-layout';
  4. let app = createApp(App);
  5. app.use(VueGridLayout); // 使用
  6. app.mount('#app');

这时候 vue-grid-layout 的组件都会挂载到全局,可以直接使用。

总结

以上是我最近从入门到实战 Vue3 全家桶的 3 个项目后总结避坑经验,其实很多都是文档中有介绍的,只是刚开始不熟悉。也希望大伙多看看文档咯~
Vue3 script-setup 模式确实越写越香。
本文内容如果有问题,欢迎大家一起评论讨论。