vue3-typescript-pro
本文最后更新于 2025-04-15,文章内容可能已经过时。
项目技术栈

项目创建
npm 创建
npm create vue@latest pnpm 创建
pnpm create vue目录结构
├── README.md
├── env.d.ts
├── eslint.config.ts
├── index.html
├── package.json
├── pnpm-lock.yaml
├── public
│ └── favicon.ico
├── src
│ ├── App.vue
│ ├── assets
│ │ ├── base.css
│ │ ├── logo.svg
│ │ └── main.css
│ ├── components
│ │ ├── HelloWorld.vue
│ │ ├── TheWelcome.vue
│ │ ├── WelcomeItem.vue
│ │ └── icons
│ │ ├── IconCommunity.vue
│ │ ├── IconDocumentation.vue
│ │ ├── IconEcosystem.vue
│ │ ├── IconSupport.vue
│ │ └── IconTooling.vue
│ ├── main.ts
│ ├── router
│ │ └── index.ts
│ ├── stores
│ │ └── counter.ts
│ └── views
│ ├── AboutView.vue
│ └── HomeView.vue
├── tsconfig.app.json
├── tsconfig.json
├── tsconfig.node.json
└── vite.config.ts项目配置
基本配置

配置页面icon
将
public -> favicon.ico替换成自己的Icon图标即可,若名字不同,则需要在index.html中进行路径和名称调整!
配置页面tltle
同样在
index.html中修改 <title></title>标签中的内容即可!
配置路径别名
在
vite.config.ts配置文件中,添加resolve路径解析对象来添加路径别名!
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
],
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
},
},
})代码规范配置
editorconfig配置
EditorConfig可以帮助在不同编辑器下的实现代码风格统一配置!
root = true
[*] # 表示所有文件
charset = utf-8 # 表示字符集编码
indent_style = space # 表示缩进风格 [tab | space]
indent_size = 2 # 表示缩进大小
end_of_line = lf # 控制换行类型 [lf | cr | crlf ]
trim_traling_whitespace = true # 去除行尾的空白字符
inset_final_newline = true # 始终在文件末尾插入新的一行
[*.md] # 表示 md 文档适用以下规则
max_line_length = off
trim_traling_whitespace = false
vscode需要安装EditorConfig插件!
prettier代码格式化
prettier是一个可配置的代码格式化工具,可以通过自定义格式配置,结合编辑器实现保存时自动格式化代码!
安装
prettier插件:pnpm i prettier -D配置
prettier:新建
.prettierrc{ "useTabs": false, // 使用tab缩进还是空格缩进 "tabWidth": 2, // 缩进的大小 "printWidth": 100, // 当前行字符串的长度 "singleQuote": true, // 单引号还是双引号,true未单引号 "trailingComma": "none", // 对象属性末尾是否 追加逗号 "semi": false // 语句末尾是否添加 分号 }配置
prettier忽略文件:新建
.prettierignore/dist/* /node_modules/** /build/* /public/* **/*.md **/*.svg
可结合
vscode完成编写代码时,保存后自动格式化代码,首先需要在vscode扩展市场中安装插件
然后使用
alt+shift+f快捷方式来选择prettier进行代码格式化!配置
保存代码时,自动格式化,在settings-> 搜索format on save->勾选即可!设置
默认格式化插件, 在settings-> 搜索 ->default format-> 选择默认格式化插件即可!
eslint代码语法检查
创建项目时,已经选择eslint插件使用,因此package.json中已经有了有eslint插件的安装!
eslint是一个代码语法检测工具,主要用来检测一些不规范的代码片段,会进行警告、报错,提示!
在
vscode中安装eslint扩展插件:它可以帮我们
在编写代码时,来实时检测语法问题,并进行提示和反馈!
解决
eslint和prettier插件冲突问题pnpm i eslint-plugin-prettier -D在
eslint配置文件中添加prettier插件
项目开发准备
开发目录划分
├── api # api 业务接口请求
├── assets # 静态资源文件 打包时,会对静态资源进行打包处理,生成文件指纹
│ ├── base.css
│ ├── logo.svg
│ └── main.css
├── components # 业务公共组件
├── config # 公共配置文件
│ └── index.ts
├── hooks # 公共hooks
├── http # axios http 请求
│ ├── index.ts
│ └── intersptor.ts
├── layout # 布局相关
│ ├── header
│ ├── index.vue
│ └── sidebar
├── main.ts
├── router # 路由封装
│ ├── index.ts
│ └── intersptor.ts
├── stores # pinia 存储封装
│ └── counter.ts
├── styles # style 样式封装
│ ├── elementui.scss
│ ├── index.scss
│ └── varibales.scss
├── utils # 全局工具
│ └── index.ts
└── views # 页面
└── LoginView.vue初始化css 样式
初始化
css样式,就是重置浏览器css默认的样式,内外边距,以及ul无序列表和a标签的一些默认样式规则!
配置vscode代码片段
配置
vscode代码片段,可以在新建vue文件时,通过快捷方式来快速生成代码片段,减少重复代码编写!
以上是
配置代码片段的在线工具,将模版生成vscode代码片段所需要的格式,并在vscode中新建代码片段将生成的代码进行配置即可!

在
vscode中 在首选项中选择 ->配置代码片段->在提示框中->选择新建代码片段
{
"vue3 typescript": {
"prefix": "tsvue",
"body": [
"<template>",
" <div class=\"${1:home}\">",
" <center><h2>${1:home}</h2></center>",
" </div>",
"</template>",
"",
"<script setup lang=\"ts\">",
"</script>",
"",
"<style lang=\"scss\" scoped>",
"${1:home}{",
" border: 1px solid #333;",
"}",
"</style>"
],
"description": "vue3 typescript"
}
}代码封装
router
对
路由管理代码进行封装管理,包括路由全局导航守卫配置等!
安装
vue-routerpnpm install vue-router@4 -S在
src目录下创建router文件夹src->router并在文件夹内部创建index.ts文件以及全局守卫文件intersptor.ts,以及routes.ts路由元信息!src->router-> modules用来存储各个模块下不同路由的元信息!index.tsimport { createRouter, createWebHistory } from 'vue-router' import { setRouterIntersptor } from './intersptor' import type { App } from 'vue' import routes from './routes' const router = createRouter({ history: createWebHistory(import.meta.env.BASE_URL), routes: routes, }) // 设置路由拦截器 setRouterIntersptor(router) export function setupRouter(app: App) { app.use(router) }intersptor.tsimport type { RouteLocationNormalizedGeneric, Router } from 'vue-router' /* 不能再这里直接引入 useAuthStore,因为它还没有被定义,所以这里应该使用异步导入的方式, import { useAuthStore } from '../stores/modules/auth' ❎错的 在 app.use(pinia) 注册之前不能导入store模块 需要在拦截器中通过动态导入的方式来加载 store 模块 // const { useAuthStore } = await import('../stores/modules/auth') ✅正确 */ export async function setRouterIntersptor(router: Router) { const whiteList = ['/login'] router.beforeEach( async (to: RouteLocationNormalizedGeneric, from: RouteLocationNormalizedGeneric) => { const { useAuthStore } = await import('../stores/modules/auth') console.log('from', from) const authStore = useAuthStore() // 当前访问的是登录页,且已登录,直接跳转到首页 if (whiteList.includes(to.path) && authStore.isLogin) { return '/' } // 当前访问的是白名单路径时,即使登录状态为未登录,也可访问 if (whiteList.includes(to.path) && !authStore.isLogin) { return true } // 当前访问的不是登录页,且登录状态为未登录时,直接跳转到 login 页面 if (!whiteList.includes(to.path) && !authStore.isLogin) { return '/login' } return true }, ) }routes.ts使用
import.meta.glob方法来批量导入modules下路由原信息import type { RouteRecordRaw } from 'vue-router' /** * @description 导入模块路由 * @param routes 路由数组 * @returns Array<RouteRecordRaw> */ function importRouteModules(routes: Array<RouteRecordRaw> = []) { const modules: Record<string, any> = import.meta.glob('./modules/*.ts', { eager: true }) Object.keys(modules).forEach((key) => { const route = modules[key].default if (Array.isArray(route)) { routes.push(...route) } else { routes.push(route) } }) return routes } // 生成路由 const routes: Array<RouteRecordRaw> = importRouteModules([]) export default routes
在
main.ts入口文件中引入routerimport { createApp } from 'vue' import App from './App.vue' import router from './router' const app = createApp(App) app.use(router) app.mount('#app')
pinia
pinia是全局状态管理插件,可以帮我们实现全局数据存储,只需要pinia是不太行的,当页面刷新的时候,丢失数据,所以还需要配合其它插件(pinia-plugin-persistedstate)来完成数据持久存储!
安装
pinia和持久化插件pnpm install pinia -S pnpm install pinia-plugin-persistedstate -D在
src目录下创建stores文件夹src->stores其中包含modules模块文件夹,以及index.ts文件index.ts用来创建pinia实例,并实现持久化配置!modules文件夹用来存放其它子模块,并在index.ts中分别导入!index.tsimport { createPinia } from 'pinia' import piniaPluginPersistedstate from 'pinia-plugin-persistedstate' import type { App } from 'vue' // 引入持久化插件 const pinia = createPinia() export function setupPinia(app: App<Element>) { app.use(pinia) } // 持久化 pinia.use(piniaPluginPersistedstate) // 用户 export * from './modules/counter' // 系统 // export * from './modules/system' export { pinia }./modules/counter.tsimport { ref, computed } from 'vue' import { defineStore } from 'pinia' export const useCounterStore = defineStore('counter', () => { const count = ref(0) const doubleCount = computed(() => count.value * 2) function increment() { count.value++ } return { count, doubleCount, increment } }, { persist: true, })
在
main.ts入口文件中引入piniaimport { createApp } from 'vue' import { setupPinia } from '@/stores/index.ts' import App from './App.vue' const app = createApp(App) setupPinia(app) app.mount('#app')
axios
axios是网络通信插件,关于axios的代码封装,简单将axios拦截器部分单独拆分出来,另外对axios的做一个公共的配置!
安装axios插件
pnpm install axios -S在
src目录下新建http文件夹并在文件夹内部分别创建
index.ts以及interspetor.ts文件index.ts是创建axios实例一部分和公共配置的相关代码!interspetor.ts时从主文件中,抽离出拦截器相关的文件,对拦截器做一个单独逻辑编写!index.tsimport axios from 'axios' import { setupInterceptor } from './intersptor' const instance = axios.create({ baseURL: import.meta.env.VITE_BASE_API, timeout: 10000, headers: { 'Content-Type': 'application/json;charset=UTF-8', }, }) // 注册拦截器 setupInterceptor(instance) export default instanceinterspector.tsimport type { AxiosInstance } from 'axios' export function setupInterceptor(instance: AxiosInstance) { // 请求拦截器 instance.interceptors.request.use( (config) => { // 在发送请求之前做些什么 return config }, (error) => { // 对请求错误做些什么 return Promise.reject(error) }, ) // 响应拦截器 instance.interceptors.response.use( (response) => { // 对响应数据做点什么 return response }, (error) => { // 对响应错误做点什么 return Promise.reject(error) }, ) }
styles
layout
ElementPlus
安装插件
pnpm install element-plus -S全局引入
在
main.ts入口文件中引入element-plus以及全局样式!
import ElementUi from 'element-plus'
import 'element-plus/dist/index.css'
import App from './App.vue'
const app = createApp(App)
app.use(ElementUi)
全局引入会将所有的组件以及样式在入口文件进行引入,会影响打包速度以及体积大小!
按需引入
按需引入,需要
安装自动导入插件,来帮我们实现自动组件导入!
pnpm install -D unplugin-vue-components unplugin-auto-import unplugin-element-plus插件地址,可以查看具体使用方式!
在
vite中,引入引入两个自动导入组件插件!
import { fileURLToPath, URL } from 'node:url'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
// https://vite.dev/config/
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
// element plus 样式自动导入
ElementPlus({
useSource: true,
}),
// 配置自动导入,ref reactive 时自动导入vue
AutoImport({
imports: ['vue', 'vue-router'],
dts: true,
resolvers: [ElementPlusResolver()],
}),
// 配置组件自动导入
Components({
dts: true,
resolvers: [ElementPlusResolver()],
extensions: ['vue'],
dirs: ['src/components/'],
}),
],
})配置
types类型声明,在使用自动导入时,会帮我们创建两个声明文件,
"auto-imports.d.ts", "components.d.ts"需要在
tsconfig.app.json中加入两个声明文件!
{
"extends": "@vue/tsconfig/tsconfig.dom.json",
"include": ["env.d.ts", "auto-imports.d.ts", "components.d.ts", "src/**/*", "src/**/*.vue"],
"exclude": ["src/**/__tests__/*"],
"compilerOptions": {
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
"noImplicitThis": true,
"paths": {
"@/*": ["./src/*"]
}
}
}加入
声明文件后,对于unknow未知类型的标签,就会有对应的类型提示了
组件封装
封装常用的全局组件
svg-icon
全局
icon组件, 需要安装的插件vite-plugin-svg-icons | unplugin-icons
pnpm install -D vite-plugin-svg-icons | unplugin-icons在
vite.config.ts中引入插件,并配置icon路径!
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import vueDevTools from 'vite-plugin-vue-devtools'
import Icons from 'unplugin-icons/vite'
import { createSvgIconsPlugin } from 'vite-plugin-svg-icons'
export default defineConfig({
plugins: [
vue(),
vueDevTools(),
// 配置icons
Icons({
autoInstall: true,
}),
createSvgIconsPlugin({
// 指定需要缓存的图标文件夹
iconDirs: [fileURLToPath(new URL('./src/icons', import.meta.url))],
// 指定symbolId格式
symbolId: 'icon-[dir]-[name]',
}),
],
)}创建
svg-icon 组件,src -> components -> svg-icon -> index.vue
<template>
<svg aria-hidden="true" class="svg-icon" :style="'width:' + size + ';height:' + size">
<use :xlink:href="symbolId" :fill="color" />
</svg>
</template>
<script setup lang="ts" name="SvgIcon">
import { computed } from 'vue'
const props = defineProps({
prefix: {
type: String,
default: 'icon',
},
iconClass: {
type: String,
required: false,
default: '',
},
color: {
type: String,
default: '',
},
size: {
type: String,
default: '1em',
},
})
const symbolId = computed(() => `#${props.prefix}-${props.iconClass}`)
</script>
<style scoped>
.svg-icon {
display: inline-block;
width: 1em;
height: 1em;
overflow: hidden;
vertical-align: -0.15em; /* 因icon大小被设置为和字体大小一致,而span等标签的下边缘会和字体的基线对齐,故需设置一个往下的偏移比例,来纠正视觉上的未对齐效果 */
outline: none;
fill: currentColor; /* 定义元素的颜色,currentColor是一个变量,这个变量的值就表示当前元素的color值,如果当前元素未设置color值,则从父元素继承 */
}
</style>
在
src目录下 -> 创建icons文件夹,用来存放 svg icon图标!

在
main.ts中导本地icons
import 'virtual:svg-icons-register'
import 'normalize.css'
import '@/styles/index.scss'
/* import ElementUi from 'element-plus'
import 'element-plus/dist/index.css' */
import { createApp } from 'vue'
import { setupPinia } from '@/stores/index.ts'
import App from './App.vue'
import router from './router'
// 本地SVG图标
import 'virtual:svg-icons-register'
const app = createApp(App)
setupPinia(app)
app.use(router)
// app.use(ElementUi)
app.mount('#app')知识点汇总
子组件实例类型定义
当通过
ref来获取子组件实例类型时,通过InstanceType来定义该组件实例的类型!
<template>
<div id="layout">
<el-container>
<Sidebar ref="sidebarRef" />
<el-container class="is-vertical">
<Navbar />
<Main />
</el-container>
</el-container>
</div>
</template>
<script setup lang="ts">
import Navbar from './navbar/Navbar.vue'
import Sidebar from './sidebar/Sidebar.vue'
import Main from './main/Main.vue'
const sidebarRef = ref<InstanceType<typeof Sidebar>>()
</script>
<style lang="scss" scoped></style>以上获取
Sidebar子组件实例,通过InstanceType来定义该组件实例类型!
const sidebarRef = ref<InstanceType<typeof Sidebar>>()
