# 项目搭建
# 前言
Vue3 (opens new window) 搭建项目不在像 Vue2 使用vue-cli脚手架创建项目,官方提供了新的构建工具 Vite (opens new window) 来创建项目,并使用Vue3+Vite+Ts 提供新的格式书写与风格指南,在使用之前请先熟悉和阅读 Typescript (opens new window) 和Vue3相关的文档,最好也看一下Vite官网。选择和使用Vue3也是一种趋势,我们就需要更加开放的拥抱Vue3。下面就让我们看看如何使用Vue3+Vite+Ts+Arco搭建一个后台管理系统的。
# 一、构建项目
Vue3官方推荐使用Vite来构建项目,而不再使用webpack,其对应的脚手架也不再使用vue-cli,虽然vue-cli也可以构建过渡版。这里针对Vite的生态还不是很完善,所以选择项目构建时要有所区分。
使用Vite构建项目Vue官方和Vite都提供了构建命令,两者构建项目都可以,区别就是Vue官方提供的构建命令更针对与Vue开发,我们使用Vue官方提供的构建命令就可以了。
# 1、创建项目
Vue3创建项目只需安装好Node后直接执行创建项目的命令即可,选择基于TS+组合式API进行开发,不再使用选项式。
npm create vue@latest
执行后,根据提示进行选择,把TS选项选上。
√ Project name: ... vue-project
√ Add TypeScript? ... No / (Yes)
√ Add JSX Support? ... No / (Yes)
√ Add Vue Router for Single Page Application development? ... No / (Yes)
√ Add Pinia for state management? ... No / (Yes)
√ Add Vitest for Unit Testing? ... (No) / Yes
√ Add an End-to-End Testing Solution? » (No)
√ Add ESLint for code quality? ... No / (Yes)
Scaffolding project in ./<your-project-name>...
Done.
前几个选项都默认安装就行了,而Eslint和Prettier选项对于没有严格要求的项目可以直接选择Yes,它只会进行简单的代码检查,不会有严格的风格要求。对于团队来说不建议使用这种,默认选择No不添加,在项目创建好后手动配置Eslint来满足需要的风格指南。
> cd vue-project
> npm install
> npm run dev
# 2、项目结构设计
项目目录创建好后,构建工程化如下。
├── public # 资源文件
│ └── favicon.ico
├── src
│ ├── api # Api ajax 等
│ ├── assets # 本地静态资源
│ │ ├── images # 项目图片
│ │ ├── css # reset、common样式
│ │ ├── font # 字体库
│ │ ├── iconfont # iconfont库
│ │ └── less/sass # 公共less/sass文件
│ ├── components # 业务公共组件
│ ├── config # 基础配置
│ ├── constants # 常量文件
│ ├── directive # 自定义指令
│ ├── hooks # hook函数
│ ├── json # json文件
│ ├── layouts # 基础布局
│ ├── mock # mock假数据
│ ├── router # Vue-Router
│ ├── stores # pinia
│ ├── types # 声明文件
│ ├── utils # 工具库
│ ├── views # 业务页面入口和常用模板
│ ├── App.vue # Vue 模板入口
│ └── main.ts # Vue 入口 TS
├── .env # 环境配置文件
├── .env.development
├── .eslintignore # eslint忽略文件
├── .eslintrc.cjs # eslint配置
├── .gitignore # git忽略
├── env.d.ts # 全局声明文件
├── .gitignore # git忽略
├── index.html # Vue 入口模板
├── package.json # 包管理
├── README.md
├── tsconfig.app.json # ts配置
├── tsconfig.json # ts配置
├── tsconfig.node.json # ts配置
└── vite.config.ts # vite配置
# 3、IDE支持
推荐使用 Visual Studio Code 进行开发,在VSCode中通过Volar (opens new window) 和TypeScript Vue Plugin (opens new window)两个插件对代码进行类型检查,这俩插件现已弃用,在VSCode中通过Vue - Official (opens new window) 插件进行代码检查。在开发中可以很大程度的帮助检查和提示类型出错,如果想要更严格的类型检查,可以通过Eslint+TS相关的代码检查插件进行控制。
新版本只需安装 Vue - Official 插件即可,Volar Takeover模式也无需开启,把TypeScript and JavaScript Language Features服务启动。
Volar是官方的 VSCode 扩展,提供了 Vue 单文件组件中的 TypeScript 支持,Volar 取代了我们之前为 Vue 2 提供的官方 VSCode 扩展 Vetur。如果之前已经安装了 Vetur,需要在Vue3项目中禁用。TypeScript Vue Plugin针对*.vue 文件进行类型检查。
# 开启Volar Takeover 模式
在添加Volar插件后~~,Volar 提供了一个叫做“Takeover 模式”的功能。在这个模式下,Volar 能够使用一个 TS 语言服务实例同时为 Vue 和 TS 文件提供支持,要开启这个模式需要在VSCode中禁用默认的TS语言服务。~~
在当前项目的工作空间下,用Ctrl + Shift + P (mac:Cmd + Shift + P) 唤起命令面板。输入built,然后选择“Extensions:Show Built-in Extensions”。在插件搜索框内输入typescript (不要删除 @builtin 前缀)。点击“TypeScript and JavaScript Language Features”右下角设置,然后选择禁用。重新加载工作空间。Takeover 模式将会在你打开一个 Vue 或者 TS 文件时自动启用。
# 二、Vite相关配置
以前基于webpack的配置现在全部改为基于Vite的配置,所以很多配置都有所改变,最主要的改变就是环境变量、静态资源、config配置等。
# 1、vite.config.ts
Vue3中关于项目的配置全部写在vite.config.ts中,很多基础配置和以前的vue.config.js差不多,具体API可以参考 配置 Vite (opens new window)。
// vite.config.ts
import path from 'path'
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import eslintPlugin from 'vite-plugin-eslint'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
// https://vitejs.dev/config/
export default defineConfig({
// base: '/',
// 本地服务启动配置、跨域配置
// server: {
// host: 'localhost',
// port: 8080,
// proxy: {
// '/api': {
// target: 'http://192.168.100.14:3351',
// ws: false,
// changeOrigin: true
// }
// }
// },
// 插件配置
plugins: [
vue(),
vueJsx(),
// 配置vite在运行的时候自动检测eslint规范
eslintPlugin({
include: ['src/**/*.ts', 'src/**/*.js', 'src/**/*.vue', 'src/*.ts', 'src/*.js', 'src/*.vue']
}),
// 自定义组件名称
VueSetupExtend()
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
// less、sass配置
css: {
preprocessorOptions: {
// 配置less全局使用
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve('src/assets/less/main.less')}";`,
},
javascriptEnabled: true
}
}
}
})
# 2、环境变量
Vite在环境变量.env等文件里配置变量统一使用 VITE_ 进行开头。
// .env
VITE_ENV=production
VITE_APP_API_BASE_URL=/api
// .env.development
VITE_ENV=development
VITE_APP_API_BASE_URL=http://192.168.100.1
获取时通过一个特殊的 import.meta.env 对象上暴露环境变量。不在是以前的process.env对象。
// 创建 axios 实例
const request: AxiosInstance = axios.create({
// API 请求的默认前缀
baseURL: import.meta.env.VITE_APP_API_BASE_URL,
timeout: 1000 * 60 * 10 // 请求超时时间
})
// 判断环境是开发环境时
if (import.meta.env.VITE_ENV === 'development') {
console.log(1111)
}
在HTML中也能使用环境变量,import.meta.env 中的任何属性都可以通过特殊的 %ENV_NAME% 语法在 HTML 文件中使用。
<h1>当前模式: %VITE_ENV %</h1>
<p>当前接口地址: %VITE_APP_API_BASE_URL%</p>
# 3、静态资源处理
在HTML中引入assets和public是和以前一样的,主要是在js中动态引入方式不同,以前通过requier,现在需要通过 new URL() 方式引入。
<template>
<div v-for="(src, idx) in imageSrc" :key="idx">
<img :src="src" style="width: 100%" />
</div>
</template>
<script setup lang="ts">
const imageSrc = [
new URL('@/assets/images/banner01.png', import.meta.url).href,
new URL('@/assets/images/banner02.png', import.meta.url).href,
new URL('@/assets/images/banner03.png', import.meta.url).href,
new URL('@/assets/images/banner04.png', import.meta.url).href,
new URL('@/assets/images/banner05.png', import.meta.url).href
]
const imgUrl = new URL('./img.png', import.meta.url).href
</script>
<style lang="less" scoped>
</style>
图片地址不能直接支持动态参数,不能整个使用变量替换,仅支持模板字符串写法。
function getImageUrl(name) {
return new URL(`./dir/${name}.png`, import.meta.url).href
}
# 4、使用插件
plugins插件配置就相当于以前webpack的各种loader一样,针对各种文件或者内容进行处理,然后在打包,Vite虽然构建速度快,但是它的插件生态却不如webpack完善。插件在vite.config.ts中进行引入和配置。
import path from 'path'
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
// 引入插件
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import eslintPlugin from 'vite-plugin-eslint'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
// https://vitejs.dev/config/
export default defineConfig({
// 插件配置
plugins: [
vue(),
vueJsx(),
// 配置vite在运行的时候自动检测eslint规范
eslintPlugin({
include: ['src/**/*.ts', 'src/**/*.js', 'src/**/*.vue', 'src/*.ts', 'src/*.js', 'src/*.vue']
}),
// 自定义组件名称
VueSetupExtend()
]
})
# 三、TS使用指南
在项目中全部使用ts文件进行开发,不在使用js。开发TS项目时,需要配置TS相关的内容,Vite为我们预先在tsconfig.json文件中配置了,我们只需在其中修改即可。并且VScode的Volar和Eslint都提供了对TS进行代码校验,在开发中需要进行类型校验的都会给与提示和补全,根据这些提示进行修改可以很大程度帮助我们解决报错和标红。参考链接:TS项目配置 (opens new window)。
# 1、ts配置文件
tsconfig.json就是ts的配置文件,放在根目录,它指定了用来编译该项目的根文件和编译选项,所有ts相关的配置都放在其中,在Vite中配置文件被拆出tsconfig.app.json和tsconfig.node.json两个文件进行引入,在 tsconfig.app.json中是基础配置。
在tsconfig.app.json配置文件中 include 和 exclude 选项可以指定该项目需要使用ts进行编译的文件和不需要编译的文件,compilerOptions中是其他相关配置。如果在项目中新建的文件无法被ts所识别第一考虑就是此配置中添加include。
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "src/**/*", "src/**/*.vue", "src/**/*.json", "src/**/*.d.ts", "./.eslintrc.cjs", "./vite.config.ts"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"composite": true,
"resolveJsonModule": true,
"esModuleInterop": true,
"baseUrl": ".",
"paths": {
"@/*": ["./src/*"]
}
}
}
# 2、声明文件
在开发时,我们可以为变量或者函数定义类型和返回值进行类型约束,定义什么就约束什么,这是针对当前所开发的代码,如果像是第三方库和插件、全局变量和方法或者开发项目的宿主环境,在整个项目中都有使用和贯穿,使用它的变量和方法肯定需要进行类型验证,我们无法在每个使用的代码中为其声明类型,这时就需要通过声明文件对其变量或方法进行类型声明,无论在哪使用都能进行类型检查。
ts的声明文件是以 .d.ts 为后缀的文件,开发者在声明文件中编写类型声明,ts根据声明文件的内容进行类型检查,然而在有些文件或者第三方库中是没有类型声明的,没有支持ts的写法,在使用变量、调用函数、实例化类时ts识别不了,无法通过类型检查,就会有一大堆报错。所以要在声明文件中编写第三方库的类型声明,让ts可以正常地进行类型检查,才能获得对应的代码补全、接口提示等功能。
一般流行的第三方库都支持ts的类型声明,安装时如果默认没有声明文件,可以去
@types organization (opens new window) 上去搜对应的声明文件,搜索加上@types,通过 npm install @types/模块名 -D 去下载其社区为其声明的文件,声明文件的包在@/node_modules/@types里,要是一些比较冷门的库可能没有,我们就要手动为其创建声明文件了,也可以声明好后发布到npm插件库中。
参考链接:TS声明文件 (opens new window)
添加声明文件或全局公共类型标注文件,一般约定在src目录下创建types文件夹来保存文件,创建好后,在项目根目录下有一个 env.d.ts 文件,这个文件是全局声明文件的入口,可以在此文件中直接引入其他 .d.ts 文件,然后就会全局生效。env.d.ts在tsconfig.json中通过include引入解析。
// eslint-disable-next-line @typescript-eslint/triple-slash-reference
/// <reference types="vite/client" />
/// <reference types="./src/types/global.d.ts" />
/// <reference types="./src/types/api.d.ts" />
/**
* 全局声明文件
* 在此自定义一些全局声明或者引入声明文件
* 在此通过reference引入types中的声明文件
* <reference types="./src/types/api.d.ts" />
* */
// store声明
// declare module 'store'
如果不想在env.d.ts 文件中一个一个引入,也可以直接在tsconfig配置include中直接通过匹配符直接匹配解析全部的.d.ts文件。
// 通过通配符匹配src目录下的文件
{
"include": [ "src/**/*.d.ts"],
}
# 3、搭配Vue使用
在 <script setup>
使用ts只需要在上面添加 lang="ts" 即可。
<template>
<!-- 启用了类型检查和自动补全 -->
{{ count.toFixed(2) }}
</template>
<script setup lang="ts">
// 启用了 TypeScript
import { ref } from 'vue'
const count = ref(1)
</script>
为ref和reactive添加类型校验,定义基础数据类型时一般会自动推导,可以不用为其标注类型,但定义引用类型时最好是加上类型标注。针对是使用ref还是reactive,网上争论还是很多,因为reactive的局限性比较多,赋值或解构操作都会使其失去响应性,一般我们推荐以ref类型居多,reactive辅助。在定义基础类型和数组我们使用ref来定义,对象的话看情况,如果这个对象会被重新赋值则还是继续使用ref,如果只是维护一些字段就使用reactive,但不要为了方便把所有的数据都塞到一个reactive对象中去,组合式API就是为了拆分细粒度,不同的逻辑放在不同位置,全都塞到一起反而本末倒置,这也是vue官方所不推崇的。
<script setup lang="ts">
import { ref, reactive } from 'vue'
import type { Ref } from 'vue'
const title = ref<string>('首页')
const status: Ref<undefined | number> = ref('2020')
// 简写
const title = ref('首页')
const status = ref<undefined | number>(0)
// 不给初始值会自动转为包含 undefined 的联合类型
// 推导得到的类型:Ref<number | undefined>
const status = ref<number>()
// 定义对象
interface StateData {
name: string;
count: number;
type: undefined | '';
date?: string;
[key: string]: any
}
const state = reactive<StateData>({
name: 'xxx',
count: 0,
type: undefined,
data: '2023-01-01',
status: 0
})
// 定义数组
const tableData = ref<StateData[]>([])
tableData.push(state)
</script>
如果定义的数据类型在多个文件中使用,我们可以把它提出来,放在全局公共的types文件中,如果在当前页面多个组件中使用,可以在index.vue目录创建 types.ts 文件来保存公共的数据类型进行导出,就像局部组件和公共组件一样,然后在通过import type 进行引入。
// types.ts
// 筛选条件
export interface FilterData {
name: string
age: number
class: string
type: undefined | ''
date: string
status: undefined | ''
}
// table数据
export interface TableInfo {
key: string
name: string
salary: number
address: string
email: string
type: number | undefined
}
然后在页面中引入
import type { FilterData, TableInfo } from './types'
# 4、获取插件声明文件
在使用插件提供的属性或方法时,或者定义插件所需要的数据格式,都会进行ts类型校验,这时需要引入插件所提供的类型进行声明,如果类型不存在我们需要自定义类型声明,也可以根据已有的类型声明导入组装成新的声明。
一般安装@types/xxx后都会有相关的类型提示,直接输入对应的名称就可以看到,如果没有就去node_modules相关的包里去找,搜索对应的类型,无法引入的就全复制出来自己定义声明文件。
<template>
<a-form ref="formRef" :model="formData" :label-col-props="{ span: 4 }" :wrapper-col-props="{ span: 18 }">
<a-form-item label="Name" field="name" :validate-trigger="['blur']">
<a-input v-model="formData.name" :max-length="50" show-word-limit placeholder="请输入" allow-clear />
</a-form-item>
</a-form>
</template>
<script setup lang="ts">
import { reactive, ref, getCurrentInstance, onMounted } from 'vue'
// 引入Form表单类型
import type { FormInstance } from '@arco-design/web-vue/es/form'
// 使用引入的类型定义ref
const formRef = ref<FormInstance>()
// 确认添加
const confirmSubmit = () => {
// 识别插件类型并调用方法
formRef.value?.validate(errors => {
if (!errors) {}
})
}
</script>
自定义一些常见的声明
// 申明外部 npm 插件模块
declare module 'splitpanes'
declare module 'js-cookie'
declare module '@wangeditor/editor-for-vue' {
import { DefineComponent } from 'vue'
const Editor: DefineComponent<{}, {}, any>
const Toolbar: DefineComponent<{}, {}, any>
export { Editor, Toolbar }
}
// 声明一个模块,防止引入文件时报错
declare module '*.json'
declare module '*.png'
declare module '*.jpg'
declare module '*.scss'
declare module '*.ts'
declare module '*.js'
// 声明文件,*.vue 后缀的文件交给 vue 模块来处理
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
// 声明文件,定义全局变量
/* eslint-disable */
declare interface Window {
isMobile: boolean
}
# 5、代码风格指南
<script setup lang="ts" name="Home">
/**
* @desc 注释
* @author xxx
* */
import { ref, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { useAppStore } from '@/stores/modules/app'
import useMouse from '@/hooks/mouse'
import useGlobalProperties from '@/hooks/globalProperties'
import type { FormInstance } from '@arco-design/web-vue/es/form'
import type { UserInfo } from '../types'
import ArcoIcon from '@/components/ArcoIcon'
import Ellipsis from '@/components/Ellipsis/index'
const router = useRouter()
const appStore = useAppStore()
const { global } = useGlobalProperties()
// 接收参数
const props = defineProps<{
title: string
likes?: number
}>()
// 定义事件
const emit = defineEmits<{
CLOSE_EVENT: [id?: number, name: string]
}>()
const formRef = ref<FormInstance>()
const title = ref<string>('首页')
const msg = ref<string>('hello world!')
const userInfo = reactive<UserInfo>({
name: 'xxx',
age: 18
})
// 增删改
const updateDialog = reactive({
visible: false,
id: ''
})
const addData = () => {
updateDialog.visible = true
}
const editData = (id: string) => {
updateDialog.visible = true
updateDialog.id = id
}
const delData = (id: string) => {
updateDialog.visible = true
updateDialog.id = id
}
// 其他功能模块
...
// 其他功能模块
...
</script>
# 四、代码检查
# 五、项目配置
# 1、Router引入
2、Pinia引入
3、Aixos封装
# 4、Less引入
安装后,在vite.config.ts中进行配置。
import path from 'path'
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
// https://vitejs.dev/config/
export default defineConfig({
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
}
},
css: {
preprocessorOptions: {
// 配置less全局使用
less: {
modifyVars: {
hack: `true; @import (reference) "${path.resolve('src/assets/less/main.less')}";`,
},
javascriptEnabled: true
}
}
}
})
# 5、Mock引入
添加mock假数据基本上和Vue2一样,只要把js文件改成ts,然后返回接口修改成api接口对应的返回格式,对应的类型校验修改一下就可以了。
安装mockjs对应的声明文件:
npm i --save-dev @types/mockjs
# 6、获取全局实例
获取全局实例通过 getCurrentInstance 方法进行获取Vue实例上的方法,在每个组件中获取都要重新引入一遍太麻烦,我们把它提取出来放到hook中,在需要使用的文件引入调用就可以。
// hooks/globalProperties.ts
/**
* 全局hook
* 获取Vue实例上的全局属性
* @example 调用示例
* import useGlobalProperties from '@/hooks/globalProperties'
* const { global } = useGlobalProperties()
* global?.$message.success('操作成功')
* */
import { getCurrentInstance } from 'vue'
export default function useGlobalProperties() {
const instance = getCurrentInstance()
// const global = instance?.proxy
const global = instance?.appContext.config.globalProperties
return { global }
}
# 7、Event Bus使用
Vue2 通过再创建一个Vue实例当做桥梁来进行组件之间的相互传参,Vue3不再支持此方法,我们通过引入mitt插件来实现兄弟组件之间的事件传递。
安装:
npm install --save mitt
在 src 文件下创建 mitt 文件,然后分别创建index.ts 和 types.ts,types主要用于对提交事件进行类型标注。
// mitt/index.ts
/**
* 配置mitt公共组件传参
* @desc 统一管理全局mitt事件,如不需要可注释
* @desc 定义全局唯一Key,以MITT_开头,以区分其他常量
* @example 调用示例
* global?.$Bus.emit('MITT_GET_USERINFO', { name: 'xxx' })
* global?.$Bus.on('MITT_GET_USERINFO', (params) => { })
* global?.$Bus.off('MITT_GET_USERINFO')
* */
import mitt from 'mitt'
import type { Events } from './types'
// const emitter = mitt()
const emitter = mitt<Events>()
export default emitter
在types中定义事件名称,对所有的事件名进行统一管理。
/**
* mitt传参类型定义
* @desc 统一管理全局mitt事件,并进行类型标注
* @desc 定义全局唯一Key,以MITT_开头,以区分其他常量
* */
interface UserInfo {
name: string
}
export type Events = {
'MITT_GET_USERINFO': UserInfo,
'MITT_GET_NUMBER'?: number
}
在main.ts中引入:
// main.ts
import { createApp } from 'vue'
import { createPinia } from 'pinia'
import App from './App.vue'
import router from './router'
import './assets/main.css'
import emitter from '@/mitt'
// TS注册
// 全局属性必须要扩展ComponentCustomProperties类型才能获得类型提示
declare module 'vue' {
export interface ComponentCustomProperties {
$Bus: typeof emitter
}
}
const app = createApp(App)
app.use(createPinia())
app.use(router)
// 挂载到全局属性上
app.config.globalProperties.$Bus = emitter
app.mount('#app')
组件中使用:
<script setup lang="ts">
// A组件发送事件
import useGlobalProperties from '@/hooks/globalProperties'
const { global } = useGlobalProperties()
const sendEmit = () => {
global?.$Bus.emit('MITT_GET_USERINFO', { name: 'xxx' })
}
</script>
<script setup lang="ts">
// B组件接收事件
import { ref, onMounted, onUnmounted } from 'vue'
import useGlobalProperties from '@/hooks/globalProperties'
const { global } = useGlobalProperties()
// 接收事件
const sendMsg = ref({
name: ''
})
onMounted(() => {
global?.$Bus.on('MITT_GET_USERINFO', (params) => {
sendMsg.value = params
})
})
// 卸载事件
onUnmounted(() => {
global?.$Bus.off('MITT_GET_USERINFO')
})
</script>
# 8、依赖注入
依赖注入使用时只需要注意注入的key是唯一的就可以,并且还要提供类型标注。我们在 src 文件下创建 provide 文件统一来管理注入的key。
// provide/index.ts
/**
* 依赖注入定义唯一Key和声明类型
* @desc 定义全局唯一key,在provide和inject中引入使用
* @desc 定义key统一以PROVIDE_开头,以区分其他常量
* @example 调用示例
* import { PROVIDE_STRING } from '@/provide'
* provide(PROVIDE_STRING, 'hello world')
* const foo = inject(PROVIDE_STRING)
* */
import type { InjectionKey } from 'vue'
import type { UserInfo } from './types'
// 定义一个类型为string的key
export const PROVIDE_STRING = Symbol('STRING_KEY') as InjectionKey<string>
// 定义一个类型为UserInfo的key
export const PROVIDE_USER_INFO = Symbol('USER_INFO') as InjectionKey<UserInfo>
// ...其他key
在types文件中定义注入的的类型,然后在index中引入使用
// provide/types.ts
/**
* 依赖注入类型声明
* */
export type id = string | number
export interface UserInfo {
id: number
// name: string
// role: string
[key: string]: any
}
在组件中使用:
<script setup lang="ts" name="Home">
// 父组件
import { provide } from 'vue'
import { PROVIDE_STRING } from '@/provide'
// 依赖注入使用
provide(PROVIDE_STRING, 'hello world123')
</script>
<script setup lang="ts" name="Home">
// 子组件
import { inject } from 'vue'
import { PROVIDE_STRING } from '@/provide'
// 获取依赖注入
const foo = inject(PROVIDE_STRING)
</script>
# 9、自定义全局指令
在directive文件夹创建modules来保存指令文件,然后创建index.ts文件引入指令文件,通过插件统一进行注册,最后在main.ts中使用。
自定义指令
// directive/modules/index.ts
/**
* 自动获取焦点指令
* @description 打开页面时输入表单自动获取焦点
* @author changz
* @param {xxx} - xxx
* @example
* <a-input v-focus></a-input>
* */
import type { DirectiveBinding, VNode } from 'vue'
export default {
mounted(el: HTMLInputElement, binding: DirectiveBinding, vnode: VNode) {
if (el.tagName === 'input' || el.tagName === 'textarea') {
el.focus()
} else {
const inputDom = el.querySelector('input') ?? el.querySelector('textarea')
inputDom?.focus()
}
}
}
在index.ts引入指令并导出
// directive/index.ts
/**
* @description 定义多个全局自定义指令
* @author changz
* @example 在main.js引入之后use挂载
* */
import type { App, Directive } from 'vue'
// 导入自定义指令
import focus from './modules/focus'
import watermark from './modules/watermark'
import permission from './modules/permission'
export default {
install(Vue: App) {
Vue.directive('focus', focus as Directive)
Vue.directive('watermark', watermark as Directive)
Vue.directive('permission', permission as Directive)
}
}
在main.ts中引入
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './stores'
import directive from './directive'
const app = createApp(App)
app.use(store)
app.use(router)
app.use(directive)
app.mount('#app')
# 10、自定义组件名
Vue3默认是以文件名当作组件的name的,如果我们想自定义组件name,需要使用插件 vite-plugin-vue-setup-extend (opens new window) 来帮助我们完成。
安装:
npm i vite-plugin-vue-setup-extend -D
在vite.config.ts中引入:
import path from 'path'
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueJsx from '@vitejs/plugin-vue-jsx'
import VueSetupExtend from 'vite-plugin-vue-setup-extend'
export default defineConfig({
plugins: [
vue(),
vueJsx(),
// 自定义组件名称
VueSetupExtend()
]
})
在组件上定义name:
<template>
</template>
<!-- If you want the include property of keep-alive to take effect, you must name the component -->
<script setup lang="ts" name="Home">
</script>
<style lang="less" scoped>
</style>