Vue生态与工程化
目录
Vue CLI
Vue CLI简介
Vue CLI是Vue官方提供的脚手架工具,用于快速创建Vue项目。它提供了交互式的项目初始化、内置的开发服务器、配置加载等功能。
1 2 3 4 5 6 7 8 9 10 11 12
| npm install -g @vue/cli
vue create my-project
cd my-project npm run serve
npm run build
|
Vue CLI项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| my-project/ ├── public/ │ ├── favicon.ico │ └── index.html ├── src/ │ ├── assets/ │ │ └── logo.png │ ├── components/ │ │ └── HelloWorld.vue │ ├── router/ │ │ └── index.js │ ├── store/ │ │ └── index.js │ ├── views/ │ │ ├── About.vue │ │ └── Home.vue │ ├── App.vue │ └── main.js ├── .browserslistrc ├── .eslintrc.js ├── babel.config.js ├── package.json └── vue.config.js
|
vue.config.js配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| module.exports = { publicPath: process.env.NODE_ENV === 'production' ? '/my-app/' : '/',
outputDir: 'dist',
assetsDir: 'static',
lintOnSave: process.env.NODE_ENV !== 'production',
productionSourceMap: false,
devServer: { port: 8080, open: true, proxy: { '/api': { target: 'http://localhost:3000', changeOrigin: true, pathRewrite: { '^/api': '' } } } },
configureWebpack: { resolve: { alias: { '@': resolve('src') } } },
chainWebpack: config => { config.plugin('html').tap(args => { args[0].title = 'My App' return args }) } }
|
环境变量
1 2 3 4 5 6 7 8 9 10 11 12
| # .env - 所有环境加载 VUE_APP_BASE_API=/api VUE_APP_VERSION=1.0.0
# .env.local - 所有环境加载,git忽略 VUE_APP_CUSTOM_CONFIG=xxx
# .env.development - 开发环境 VUE_APP_ENV=development
# .env.production - 生产环境 VUE_APP_ENV=production
|
1 2
| console.log(process.env.VUE_APP_BASE_API)
|
Vite
Vite简介
Vite是新一代前端构建工具,由Vue作者尤雨溪主导开发。它利用浏览器原生ES模块实现极速的开发体验。
1 2 3 4 5 6 7 8 9 10 11 12
| npm create vite@latest my-vue3-app -- --template vue
cd my-vue3-app npm install
npm run dev
npm run build
|
Vite核心特性
| 特性 |
说明 |
| 极速冷启动 |
基于ESM,无需打包 |
| 热模块更新 |
基于ESM的HMR |
| 按需编译 |
只编译当前使用的模块 |
| 开箱即用 |
内置TypeScript、JSX、CSS支持 |
Vite配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| import { defineConfig } from 'vite' import vue from '@vitejs/plugin-vue' import path from 'path'
export default defineConfig({ plugins: [vue()],
resolve: { alias: { '@': path.resolve(__dirname, './src') } },
server: { port: 3000, open: true, proxy: { '/api': { target: 'http://localhost:8080', changeOrigin: true } } },
build: { target: 'es2015', outDir: 'dist', assetsDir: 'assets', sourcemap: false, rollupOptions: { output: { manualChunks: { 'element-plus': ['element-plus'] } } } } })
|
Vite环境变量
1 2 3 4 5 6 7 8
| # .env - 所有环境 VITE_APP_BASE_URL=/api
# .env.development VITE_APP_ENV=development
# .env.production VITE_APP_ENV=production
|
1 2
| console.log(import.meta.env.VITE_APP_BASE_URL)
|
构建工具对比
Vite vs Webpack vs Vue CLI
| 特性 |
Vite |
Webpack |
Vue CLI |
| 开发启动 |
极快(ESM) |
慢(打包) |
慢(打包) |
| HMR |
极速 |
中等 |
中等 |
| 生产构建 |
Rollup |
Webpack |
Webpack |
| 配置复杂度 |
低 |
高 |
中 |
| 生态 |
发展中 |
成熟 |
成熟 |
| Vue3支持 |
最佳 |
支持 |
支持 |
| 冷启动 |
即时 |
慢 |
慢 |
Vite工作原理
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| ┌─────────────────────────────────────────────────────────────┐ │ Vite 开发模式 │ ├─────────────────────────────────────────────────────────────┤ │ │ │ 浏览器请求 │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ Vite Dev │ │ │ │ Server │ │ │ └────────┬────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────────────────────────────────────────┐ │ │ │ 模块处理 │ │ │ │ │ │ │ │ 1. HTML -> 返回HTML │ │ │ │ 2. Vue SFC -> 编译成JS/CSS │ │ │ │ 3. CSS -> 返回CSS │ │ │ │ 4. ES Module -> 返回原模块 │ │ │ │ │ │ │ └─────────────────────────────────────────────────────┘ │ │ │ │ │ ▼ │ │ 返回浏览器 │ │ │ └─────────────────────────────────────────────────────────────┘
|
工程化最佳实践
目录结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| src/ ├── api/ # API接口 │ ├── user.js │ ├── product.js │ └── index.js ├── assets/ # 静态资源 │ ├── images/ │ └── styles/ ├── components/ # 公共组件 │ ├── BaseButton.vue │ ├── BaseInput.vue │ └── BaseModal.vue ├── composables/ # 组合式函数 (Vue3) │ ├── useMouse.js │ └── useFetch.js ├── config/ # 配置文件 │ └── index.js ├── constants/ # 常量定义 │ └── index.js ├── directives/ # 自定义指令 │ └── index.js ├── hooks/ # 生命周期钩子 (Vue2) │ └── index.js ├── layout/ # 布局组件 │ ├── MainLayout.vue │ └── BlankLayout.vue ├── router/ # 路由配置 │ ├── index.js │ └── routes/ ├── store/ # 状态管理 │ ├── modules/ │ └── index.js ├── utils/ # 工具函数 │ ├── storage.js │ └── format.js ├── views/ # 页面组件 │ ├── home/ │ │ └── index.vue │ └── user/ │ ├── index.vue │ └── detail.vue ├── App.vue └── main.js
|
API封装
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43
| import axios from 'axios' import { useAuthStore } from '@/store/auth'
const request = axios.create({ baseURL: import.meta.env.VITE_API_BASE_URL, timeout: 10000 })
request.interceptors.request.use( config => { const authStore = useAuthStore() if (authStore.token) { config.headers.Authorization = `Bearer ${authStore.token}` } return config }, error => Promise.reject(error) )
request.interceptors.response.use( response => { const { data } = response if (data.code !== 0) { Message.error(data.message) return Promise.reject(data) } return data.data }, error => { if (error.response) { const { status } = error.response if (status === 401) { const authStore = useAuthStore() authStore.logout() router.push('/login') } } return Promise.reject(error) } )
export default request
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| import request from './request'
export const getUserList = (params) => { return request.get('/users', { params }) }
export const getUserById = (id) => { return request.get(`/users/${id}`) }
export const createUser = (data) => { return request.post('/users', data) }
export const updateUser = (id, data) => { return request.put(`/users/${id}`, data) }
export const deleteUser = (id) => { return request.delete(`/users/${id}`) }
|
工具函数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| const STORAGE_PREFIX = 'APP_'
export const storage = { set(key, value) { const data = JSON.stringify(value) localStorage.setItem(STORAGE_PREFIX + key, data) },
get(key, defaultValue = null) { const data = localStorage.getItem(STORAGE_PREFIX + key) if (data === null) return defaultValue try { return JSON.parse(data) } catch { return data } },
remove(key) { localStorage.removeItem(STORAGE_PREFIX + key) },
clear() { Object.keys(localStorage).forEach(key => { if (key.startsWith(STORAGE_PREFIX)) { localStorage.removeItem(key) } }) } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| export function formatDate(date, format = 'YYYY-MM-DD HH:mm:ss') { const d = new Date(date) const year = d.getFullYear() const month = String(d.getMonth() + 1).padStart(2, '0') const day = String(d.getDate()).padStart(2, '0') const hour = String(d.getHours()).padStart(2, '0') const minute = String(d.getMinutes()).padStart(2, '0') const second = String(d.getSeconds()).padStart(2, '0')
return format .replace('YYYY', year) .replace('MM', month) .replace('DD', day) .replace('HH', hour) .replace('mm', minute) .replace('ss', second) }
export function formatFileSize(bytes) { if (bytes === 0) return '0 B' const k = 1024 const sizes = ['B', 'KB', 'MB', 'GB', 'TB'] const i = Math.floor(Math.log(bytes) / Math.log(k)) return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i] }
|
Element UI
简介
Element UI是一套为开发者、设计师和产品经理准备的基于Vue 2.0的桌面端组件库。
1 2 3 4 5
| npm install element-ui -S
npm install element-plus -S
|
基本使用
1 2 3 4 5 6 7 8 9 10 11
| import Vue from 'vue' import ElementUI from 'element-ui' import 'element-ui/lib/theme-chalk/index.css' import App from './App.vue'
Vue.use(ElementUI)
new Vue({ render: h => h(App) }).$mount('#app')
|
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import ElementPlus from 'element-plus' import 'element-plus/dist/index.css' import App from './App.vue'
const app = createApp(App) app.use(ElementPlus) app.mount('#app')
|
常用组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| <template> <el-container> <el-header>Header</el-header> <el-main>Main</el-main> </el-container>
<el-button type="primary" @click="handleClick">按钮</el-button>
<el-input v-model="input" placeholder="请输入"></el-input>
<el-table :data="tableData" stripe> <el-table-column prop="date" label="日期"></el-table-column> <el-table-column prop="name" label="姓名"></el-table-column> <el-table-column prop="address" label="地址"></el-table-column> </el-table>
<el-form :model="form" label-width="80px"> <el-form-item label="名称"> <el-input v-model="form.name"></el-input> </el-form-item> </el-form>
<el-dialog title="提示" :visible.sync="dialogVisible"> <span>这是一段信息</span> <template #footer> <el-button @click="dialogVisible = false">取消</el-button> <el-button type="primary" @click="dialogVisible = false">确定</el-button> </template> </el-dialog> </template>
|
Ant Design Vue
简介
Ant Design Vue是Ant Design的Vue实现,专注于企业级产品的UI设计。
1 2 3 4 5
| npm install ant-design-vue@next
npm install ant-design-vue
|
基本使用
1 2 3 4 5 6 7 8 9
| import { createApp } from 'vue' import Antd from 'ant-design-vue' import 'ant-design-vue/dist/reset.css' import App from './App.vue'
const app = createApp(App) app.use(Antd) app.mount('#app')
|
常用组件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| <template> <a-button type="primary" @click="handleClick">按钮</a-button>
<a-input v-model:value="input" placeholder="请输入" />
<a-table :dataSource="dataSource" :columns="columns" />
<a-modal v-model:open="visible" title="Modal" @ok="handleOk"> <p>Some contents...</p> </a-modal>
<a-form :model="form" layout="vertical"> <a-form-item label="名称"> <a-input v-model:value="form.name" /> </a-form-item> </a-form> </template>
|
项目结构
小型项目结构
1 2 3 4 5 6 7 8 9 10
| src/ ├── api/ ├── assets/ ├── components/ ├── router/ ├── store/ ├── utils/ ├── views/ ├── App.vue └── main.js
|
中型项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| src/ ├── api/ ├── components/ # 公共组件 │ ├── common/ # 通用组件 │ └── business/ # 业务组件 ├── composables/ # 组合式函数 ├── constants/ # 常量 ├── directives/ # 指令 ├── hooks/ # 钩子函数 ├── layouts/ # 布局组件 ├── router/ # 路由 │ └── modules/ # 路由模块 ├── store/ # 状态管理 │ └── modules/ # Store模块 ├── styles/ # 全局样式 ├── utils/ # 工具函数 ├── views/ # 页面 │ ├── modules/ # 按模块划分 │ └── ... └── App.vue
|
大型项目结构
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31
| src/ ├── apis/ # 接口定义 │ ├── modules/ # 按模块划分 │ └── index.ts ├── assets/ # 静态资源 │ ├── icons/ │ ├── images/ │ └── styles/ ├── components/ # 组件 │ ├── base/ # 基础组件 │ ├── business/ # 业务组件 │ └── common/ # 通用组件 ├── composables/ # 组合式函数 ├── configs/ # 配置文件 ├── constants/ # 常量定义 ├── directives/ # 指令 ├── enums/ # 枚举 ├── hooks/ # 钩子函数 ├── layouts/ # 布局 ├── mocks/ # Mock数据 ├── router/ # 路由 │ ├── guards/ # 路由守卫 │ └── routes/ # 路由配置 ├── services/ # 服务层 ├── stores/ # 状态管理 │ └── modules/ # Store模块 ├── types/ # TypeScript类型 ├── utils/ # 工具函数 ├── views/ # 页面 ├── App.vue └── main.ts
|
代码规范
ESLint配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| module.exports = { root: true, env: { browser: true, node: true, es2021: true }, extends: [ 'plugin:vue/vue3-recommended', 'eslint:recommended' ], parserOptions: { ecmaVersion: 2021, sourceType: 'module' }, rules: { 'no-console': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'no-debugger': process.env.NODE_ENV === 'production' ? 'warn' : 'off', 'vue/multi-word-component-names': 'off' } }
|
Prettier配置
1 2 3 4 5 6 7 8 9 10 11
| module.exports = { semi: false, singleQuote: true, trailingComma: 'none', printWidth: 100, tabWidth: 2, useTabs: false, arrowParens: 'avoid', endOfLine: 'auto' }
|
Git Hooks
1 2 3 4 5 6 7
| { "hooks": { "pre-commit": "lint-staged", "commit-msg": "commitlint -E HUSKY_GIT_PARAMS" } }
|
1 2 3 4 5
| module.exports = { '*.{js,vue}': ['eslint --fix', 'git add'], '*.{css,scss}': ['prettier --write', 'git add'] }
|
总结
构建工具选择
| 场景 |
推荐 |
| 新项目Vue3 |
Vite |
| Vue2项目 |
Vue CLI |
| 大型企业项目 |
Webpack |
| 快速原型 |
Vite |
| 追求构建速度 |
Vite |
UI框架选择
| 框架 |
特点 |
| Element Plus |
简洁、文档完善 |
| Ant Design Vue |
企业级、组件丰富 |
| Vuetify |
Material Design |
| Naive UI |
TypeScript优先 |
相关链接
- [Vue3新特性与Composition API](Vue3新特性与Composition API.md)
- Vue状态管理
- Vue路由