vue3使用中的常见问题

982 阅读7分钟

1、如何设置全局变量

  • main.ts 设置全局变量
import api from '@/api'

app.config.globalProperties.$api = api
  • 使用全局变量,使用 getCurrentInstance 方法一
// ctx.$api 就是全局设置的变量
const {
  proxy: { $api },
} = getCurrentInstance();

// ctx.$api 就是全局设置的变量
const { ctx } = getCurrentInstance();
  • 使用全局变量,使用 getCurrentInstance 方法二

(1) 在hooks文件夹下 创建一个 useCurrentInstance.ts 的hooks文件

// 解决ts的报错:...类型“ComponentInternalInstance | null”
// 1. 可以添加ts忽略去解决
// // @ts-ignoreconst { proxy } = getCurrentInstance();
// 但是这个方法很无脑,也麻烦
// 2. 考虑到在获取上下文和全局挂载实例的时候会用到这个getCurrentInstance,通过单独写一个ts文件来实现
import { ComponentInternalInstance, getCurrentInstance } from 'vue';

export default function useCurrentInstance() {
    const { appContext } = getCurrentInstance() as ComponentInternalInstance;
    const globalProperties = appContext.config.globalProperties;
    return {
        globalProperties,
    };
}

(1) 在对应的vue文件中使用


<script setup lang="ts">
    // 先引入文件
    import useCurrentInstance from '@/hooks/useCurrentInstance';
    
    const { globalProperties } = useCurrentInstance();
    
    // 使用(这里使用的是elementplus中的 Message组件,但是样式会丢失)
    globalProperties.$message({
            message: '1213',
            center: true,
        });
    
</script>

2、vue3中如何解决elementplus的ElMessage找不到和对应的样式丢失问题

在vue3中使用elementplus中的ElMessage,配置了自动导入,但是却还是报找不到模块

  1. 我们需要手动导入该模块
    import { ElMessage } from 'element-plus'
  2. 当我们导入后发现样式出错了,这是就需要我们配置一下自动导入样式,第一先安装依赖
    npm i vite-plugin-style-import consola -D
  3. 然后我们需要在vite.config.ts中配置一下

import {createStyleImportPlugin,ElementPlusResolve} from 'vite-plugin-style-import';
export default defineConfig({
  plugins: [
      //配置自动导入element start
      createStyleImportPlugin({
          resolves: [ElementPlusResolve()],
          libs: [
              {
                  libraryName: 'element-plus',
                  esModule: true,
                  resolveStyle: (name: string) => {
                      return `element-plus/theme-chalk/${name}.css`
                  },
              },
          ]
      }),
      //配置自动导入element end
      vue(),

  ],
})

3、vue3和typescript项目自动导入api和components

vue3日常项目中定义变量,需要在每个vue文件中,手动引入ref,reactive等等,比较麻烦,可以通过unplugin-auto-import给我们自动引入

  • 1、安装自动导入api插件unplugin-auto-import
npm install unplugin-auto-import -D

修改vite.config.ts配置,重启项目会在 src目录下生成一个 auto-import.d.ts 文件

import AutoImport from 'unplugin-auto-import/vite' //注意后面有个/vite
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const root = process.cwd()
  const env = loadEnv(mode, root)
  return {
    plugins: [
      AutoImport({
        imports: ['vue', 'vue-router'],
        // 可以选择auto-import.d.ts生成的位置,使用ts建议设置为'src/auto-import.d.ts'
        dts: 'src/auto-import.d.ts'
      }),
    ]
  }
})

AutoImport中可以有很多配置项,可以到github中看详细配置:

GitHub - antfu/unplugin-auto-import: Auto import APIs on-demand for Vite, Webpack and Rollup

注:dts是帮我们生成的类型声明文件,直接使用会找不到

上面配置完毕后会在src目录下生成一个auto-import.d.ts文件,里面帮我们自动引入vue相关内容,我们可以在项目中直接使用

使用如下

<script setup lang="ts">
  // 这里我们不用引入ref, 而是可以直接使用了
  const msg = ref<string>('Hello Vue3')
</script>
 
<template>
  {{ msg }}
</template>
 
<style scoped lang="scss"></style>
  • 2、安装自动导入组件插件unplugin-vue-components
npm install unplugin-vue-components -D

修改vite.config.ts配置,重启项目会在 src目录下生成一个 components.d.ts 文件

import Components from 'unplugin-vue-components/vite' //注意后面有个/vite
export default defineConfig(({ mode }: ConfigEnv): UserConfig => {
  const root = process.cwd()
  const env = loadEnv(mode, root)
  return {
    plugins: [
      Components({
        dirs: ['src/components'],
        extensions: ['vue', 'tsx'],
        // 配置文件生成位置
        dts: 'src/components.d.ts'
      })
    ]
  }
})

4、vue3+vite+ts报错:找不到模块“@/xxxxxx”或其相应的类型声明

情况一:引入ts文件报错找不到相应类型声明,此时是tsconfig.json文件还要进行文件系统路径别名设置

{
    "compilerOptions": {
        "baseUrl": "./"  // 解析非相对模块的基础地址,默认是当前目录
        "paths": { // 路径映射,相对于baseUrl
            "@/*": ["src/*"]
        }
    }
}
// 注意@/和src/后面的*号,如果缺少了还是会报错!!!

情况二:引入vue文件报错时,要在vite.config.ts中配置文件系统路径别名

import path from 'path'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
 
export default defineConfig({
    resolve: {
        alias: {
            '@': path.resolve(__dirname, 'src')
        } 
    },
    plugins: [
        vue()
    ]
})

5、vite-env.d.ts文件的作用

1、项目初始化后,在 src 目录下,会多一个 vite-env.d.ts 文件, 是用来进行声明的,会自动把类型添加到 vite 提供的 import.meta.env 上

2、还可以让TypeScript 识别.vue 文件

/// <reference types="vite/client" />

// 下面的代码项目初始化的时候没有,是自己添加的
// https://blog.csdn.net/ganyingxie123456/article/details/126265566
// https://blog.csdn.net/qq_31967569/article/details/127389728
// www.cnblogs.com/wisewrong/p/15971158.html
// 可以在代码中获取用户自定义环境变量的 TypeScript 智能提示
// 第一步:在tsconfig.json中添加 "types": ["vite/client"] ,用来提供import.meta.env 上 Vite 注入的环境变量的类型定义
// 第二步:加入下面代码
// 第三步:需要重启编辑器才可以生效,后面再用的时候,就可以 无限点 下去的,就有提示了
interface ImportMetaEnv {
	readonly MYPROJECT_APP_KEY: number
	readonly MYPROJECT_BASE_URL: string
	// 更多环境变量
}
interface ImportMeta {
	readonly env: ImportMetaEnv
}

// 在使用ts开发的项目中,.d.ts 结尾的文件主要用于 TypeScript 识别.vue 文件,
// .vue 文件不是一个常规的文件类型,ts 是不能理解 vue 文件是干嘛的,这里就告诉 ts,
// vue 文件是这种类型的。没有这个文件中的declare声明文件,你会发现 import 的所有 vue 类型的文件都会报错。
// 并且js本身是没有类型的,ts的语言服务需要.d.ts文件来识别类型,这样才能做到相应的语法检查和智能提示
declare module '*.vue' {
	import type { DefineComponent } from 'vue'
	// eslint-disable-next-line @typescript-eslint/ban-types
	const component: DefineComponent<{}, {}, any>
	export default component
}

6、Vue父组件引用子组件属性或方法显示undefined问题

子组件

const name = ref('张麻子666666')
const changeName = () => {
	name.value = '县长6666666'
}

// 属性和方法通过defineExpose暴露给父组件使用
defineExpose({
    name,
    changeName,
})

父组件

<template>
    <VueUseSon ref="sonComponentRef"></VueUseSon>
</template>

<script setup lang="ts">
    import VueUseSon from './vueUseSon.vue' // 引用子组件
    
    // 子组件ref(TypeScript语法)sonComponentRef必须和 ref="sonComponentRef" 名字一致
    const sonComponentRef = ref<InstanceType<typeof VueUseSon>>()
    // const sonComponentRef = ref()
    
    // 直接打印,发现获取不到子组件的属性和方法,显示undefined
    console.log(sonComponentRef)
    console.log(sonComponentRef.value?.name)
    console.log(unref(sonComponentRef)?.name)
    console.log(sonComponentRef.value?.changeName)
    // 这是由于子组件没有加载完成呢,当然获取不到
    
    // 用 nextTick 包裹一下,就可以正常获取子组件暴露的属性和方法了
    nextTick(() => {
        console.log(sonComponentRef)
        console.log(sonComponentRef.value?.name)
        console.log(unref(sonComponentRef)?.name)
        console.log(sonComponentRef.value?.changeName)
    })
</script>

7、自定义页面顶部加载数据时候的进度条

image.png

1、在components目录下添加如下目录和文件

【topLoadingBar目录、topLoadingBar.ts、topLoadingBar.vue】

image.png

2、topLoadingBar.vue文件的代码如下:

<template>
    <!-- <Teleport to="#app"> -->
    <div class="wrap_top_loading_bar">
        <div ref="loadingBarDom" class="top_loading_bar"></div>
    </div>
    <!-- </Teleport> -->
</template>
<script setup lang="ts">
const loadingBarDom = ref<HTMLElement>()
let process = ref<number>(0)
let timer = ref<number>(0)

/**进度条开始递归滚动前进 */
const startLoading = () => {
    let dom = loadingBarDom.value as HTMLElement
    process.value = 1
    // 进度条开始递归滚动前进
    timer.value = window.requestAnimationFrame(function fun() {
        if (process.value < 95) {
            process.value += 1
            dom.style.width = process.value + '%'
            timer.value = window.requestAnimationFrame(fun)
        } else {
            //滚动完成
            process.value = 0
            window.cancelAnimationFrame(timer.value)
        }
    })
}

/**进度条滚动完成 */
const endLoading = () => {
    let dom = loadingBarDom.value as HTMLElement
    process.value = 100
    // 进度条开始递归滚动前进
    window.requestAnimationFrame(() => {
        dom.style.width = process.value + '%'
        // 滚动条dom长度变成0
        setTimeout(() => {
            dom.style.width = 0 + '%'
            process.value = 0
        }, 200)
    })
}
// onMounted(() => {
    // startLoading()
    // setTimeout(() => {
        // endLoading()
    // }, 2000)
// })
defineExpose({
    startLoading,
    endLoading,
})
</script>
<style scoped lang="scss">
.wrap_top_loading_bar {
    position: fixed;
    top: 0;
    left: 0;
    width: 100%;
    height: 20px;
    // background: blue;
    .top_loading_bar {
        height: 2px;
        background: deeppink;
    }
}
</style>

3、topLoadingBar.ts文件的代码如下:

import { createVNode, render } from 'vue'
import TopLoadingBar from './topLoadingBar.vue'

export const Vnode = createVNode(TopLoadingBar)

render(Vnode, document.body)

4、router目录下的index.ts文件的主要代码如下:

// 引入
import { Vnode } from '@/components/topLoadingBar/topLoadingBar.ts'

// 使用
// 路由的全局前置守卫
router.beforeEach((to, from, next) => {
    Vnode.component?.exposed?.startLoading()
    next()
})
// 路由的全局后置守卫
router.afterEach((to, from) => {
    setTimeout(() => {
        Vnode.component?.exposed?.endLoading()
    }, 5000)
})

8、reactive响应式数据丢失的原因

 const obj = reactive({
    a:{
      name:'张三'
    },
    b:'李四'
  })
  // 解构后a是响应式数据,b就是一个普通值了,丢失了响应式
  // 这是因为reactive设计的实现原理是,如果传入的是对象,就变成响应式,
  // 如果传入的不是对象,就原样返回了,这期间会递归去判断传入的值是否为对象
  let {a,b} = obj 
  let {name} = a
  console.log('a是',a); // 解构后a是响应式数据
  console.log('b是',b); // 解构后b就是一个普通值了,丢失了响应式
  console.log('name是',name); // 解构后name就是一个普通值了,丢失了响应式
  setTimeout(()=>{
    a.name ='赵六'
    b = '王五'
    name = '改变了'
    console.log('a是',a);
    console.log('b是',b);
    console.log('name是',name);
  },2000)

9、ref响应式中初始化的是数组

import { ref, computed, reactive, unref } from 'vue';
const arrayRef = ref([1, 2, 3]);
// 直接对整个数组进行重新赋值, 不会丢失响应式,页面数据会变化
arrayRef.value = [4, 5, 6];
// 直接修改数组内部的某个元素,不会丢失响应式,页面数据会变化
setTimeout(()=>{
arrayRef.value[0] = 100;
arrayRef.value.push(400)
console.log('数组是:',arrayRef.value);
},2000)
console.log('数组是:',arrayRef.value);

10、ref响应式中初始化的是对象

let aaa = ref({
    name: '张三'
})

let {value} = aaa // 可以解构,拿到的依然是响应式数据,修改数据后,值会改变,页面数据会改变
console.log('ref响应式aaa下的值:',aaa.value);
console.log('ref响应式aaa下的值:',value);

let {name} = aaa.value // 可以解构,拿到的是普通数据,修改数据后,值会改变,页面数据不会改变
console.log('ref响应式aaa解构后的值name:',name);

setTimeout(()=>{
// value.name = '响应式李四' // 修改数据后,值会改变,页面数据会改变
console.log('ref响应式aaa的值:',aaa.value);
console.log('ref响应式aaa下的值:',value);

// 修改数据后,值会改变,页面数据不会改变,
// 如果页面数据发生改变了,一定是页面的其他响应式数据改变,
// 导致整个页面重新刷新,从而拿到了该值的最新数据
name = '李四' // 修改数据后,值会改变,页面数据不会改变,
console.log('ref响应式aaa解构后的值name:',name);
}, 2000)