vue3-typescript-pro
项目技术栈
项目创建
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-router
pnpm install vue-router@4 -S
在
src
目录下创建router
文件夹src->router
并在文件夹内部创建index.ts
文件以及全局守卫文件intersptor.ts
,以及routes.ts
路由元信息!src->router-> modules
用来存储各个模块下不同路由的元信息!index.ts
import { 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.ts
import 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
入口文件中引入router
import { 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.ts
import { 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.ts
import { 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
入口文件中引入pinia
import { 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.ts
import 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 instance
interspector.ts
import 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>>()