VueCli和Vite
构建项目
-
VueCli
Vue CLI 4.x 需要 Node.js v8.9 或更高版本 (推荐 v10 以上)npm install -g @vue/cli # OR yarn global add @vue/cli vue --version 查看vue-cli版本 vue create [projectName] 通过vue-cli创建项目
-
Vite
Vite 需要 Node.js 版本 14.18+,16+npm create vite@latest
Vite路由
配置路由
npm install vue-router@next -S 安装到生产环境
or
npm install vue-router@4
or
yarn add vue-router@4
src 新建 router/index.js
import { createRouter, createWebHistory } from 'vue-router'
// 还有 createWebHashHistory 和 createMemoryHistory
createRouter({
history: createWebHistory(),
routes: [],
})
映射路由、渲染组件
import { createRouter, createWebHistory } from 'vue-router'
const routes = [
{
path: '/home',
name: 'Home',
component: () => import('../views/Home/index.vue')
}
]
const router = createRouter({
history: createWebHistory(),
routes
})
export default router
新建src/views/Home/index.vue
<template>
<div>这是Home组件</div>
</template>
src/App.vue 设置渲染区域
<script setup>
</script>
<template>
<router-view></router-view>
</template>
<style scoped>
</style>
main.js 引入路由
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import router from './router'
const app = createApp(App)
app.use(router).mount('#app')
Vite Vuex
安装vuex
npm install vuex@next --S 安装到生产环境
or
yarn add vuex
main.js 引入路由
import { createApp } from 'vue'
import App from './App.vue'
// 引入路由
import router from './router'
+ // 引入store
+ import store from './store'
const app = createApp(App)
- app.use(router).mount('#app')
M app.use(router).use(store).mount('#app')
新建store文件,src/store/index.js
import { createStore } from 'vuex'
const store = createStore({
state () {
return { }
},
mutations: {},
getters:{},
actions:{},
modules:{}
})
export default store
Vuex的基础知识
数据主要存在State中
- 组件直接调用Actions,Actions再去调用Mutations,Mutations再去修改State
- 或者组件直接调用Mutations,Mutations再去修改State
- 要更改 state 必须通过 Mutations 去做更新
定义number,通过 Mutations 去更新,src/store/index.js
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
+ number: 0
}
},
mutations: {
+ UPDATE_NUMBER (state, value) {
+ state.number = value
+ }
},
getters:{},
actions:{},
modules:{}
})
export default store
使用mutations(commit): src/views/Home/index.vue
<template>
<div>这是Home组件</div>
+ <div>这是Store的数据:{{number}}</div>
</template>
+ <script setup>
+ import { computed } from 'vue'
+ import { useStore } from 'vuex'
+ const store = useStore()
+ setTimeout(() => {
+ store.commit('UPDATE_NUMBER',50)
+ }, 2000)
+ const number = computed(() => store.state.number)
+ </script>
Vuex的Actions
- mutations 是同步的
- actions 是异步的
src/store/index.js
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
number: 0
}
},
+ getters:{},
+ // 同步
mutations: {
+ // store.commit('UPDATE_NUMBER',50)
UPDATE_NUMBER (state, value) {
state.number = value
}
},
- getters:{},
// 异步
actions:{
+ // store.dispatch("NUMBERACTION",200) 调用
+ // 方法一:
+ // NUMBERACTION(context,payload){
+ // context.commit('UPDATE_NUMBER',payload)
+ // console.log(111,context.state.number) // 想获取state的数据
+ // }
+ // 方法二(拓展方式):
M NUMBERACTION({state,commit},payload){
M commit('UPDATE_NUMBER',payload)
M console.log(111,state.number) // 想获取state的数据
M }
},
modules:{}
})
export default store
使用actions(dispatch): src/views/Home/index.vue
<template>
<div>这是Home组件</div>
<div>这是Store的数据:{{number}}</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
setTimeout(() => {
M // store.commit('UPDATE_NUMBER',50)
+ store.dispatch('NUMBERACTION',200)
}, 2000)
const number = computed(() => store.state.number)
</script>
Vuex的Actions异步 Promise
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
number: 0
}
},
getters:{},
// 同步
mutations: {
UPDATE_NUMBER (state, value) {
state.number = value
}
},
// 异步
actions:{
NUMBERACTION({state,commit},payload){
+ return new Promise((resolve,reject)=>{
commit('UPDATE_NUMBER',payload)
console.log(111,state.number) // 想获取state的数据
+ resolve() // 成功回调
+ // reject() // catch回调
+ })
}
},
modules:{}
})
export default store
使用Promise:src/views/Home/index.vue
<template>
<div>这是Home组件</div>
<div>这是Store的数据:{{number}}</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
setTimeout(() => {
// store.commit('UPDATE_NUMBER',50)
M store.dispatch('NUMBERACTION',200).then(response => {
+ console.log(response)
+ }).catch((error)=>{
+ console.log(error)
+ })
}, 2000)
const number = computed(() => store.state.number)
</script>
Vuex的Getters 计算属性
import { createStore } from 'vuex'
const store = createStore({
state () {
return {
number: 0
}
},
+ // 可以做一些计算属性,最终返回的是最新的结果,不会影响state里面数据
getters:{
+ getNumber:(state) => {
+ return state.number + 10
+ }
},
// 同步
mutations: {
UPDATE_NUMBER (state, value) {
state.number = value
}
},
// 异步
actions:{
NUMBERACTION({state,commit},payload){
return new Promise((resolve,reject)=>{
commit('UPDATE_NUMBER',payload)
console.log(111,state.number) // 想获取state的数据
resolve() // 成功回调
// reject() // catch回调
})
}
},
modules:{}
})
export default store
使用Vuex的Getters
- src/views/Home/index.vue
<template>
<div>这是Home组件</div>
<div>这是Store的数据:{{number}}</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
setTimeout(() => {
// store.commit('UPDATE_NUMBER',50)
M // store.dispatch('NUMBERACTION',200).then(response => {
M // console.log(response)
M // }).catch((error)=>{
M // console.log(error)
M // })
+ const result = store.getters['getNumber']
+ console.log('result',result)
}, 2000)
const number = computed(() => store.state.number)
</script>
Vuex的Modules模块化
解决多业务模块在做state存储时,字段命名冲突
- 新建store/modules/文件夹,并新建文件a.js,b.js:
a.js:
+ const state = {
+ name: 10
+ }
+ const getters = {}
+ const mutations = {
+ UPDATE_NUMBER (state, value) {
+ state.number = value
+ }
+ }
+ const actions = {
+ NUMBERACTION (context, payload) {
+ return new Promise((resolve,reject)=>{
+ commit('UPDATE_NUMBER',payload)
+ console.log(111,state.number) // 想获取state的数据
+ resolve(1000) // 成功回调
+ // reject(1000) // catch回调
+ })
+ }
+ }
+ export default {
+ namespaced: true, // 命名空间
+ state,
+ getters,
+ mutations,
+ actions
+ }
b.js:
+ const state = {
+ name: 20
+ }
+ const getters = {}
+ const mutations = { }
+ const actions = { }
+ export default {
+ namespaced: true, // 命名空间
+ state,
+ getters,
+ mutations,
+ actions
+ }
- 在store/index.js下导入a.js,b.js:
import { createStore } from 'vuex'
+ import A from "./modules/a"
+ import B from "./modules/b"
const store = createStore({
- state () {
- return {
- number: 0
- }
- },
- // 可以做一些计算属性,最终返回的是最新的结果,不会影响state里面数据
- getters:{
- getNumber:(state) => {
- return state.number + 10
- }
- },
- // 同步
- mutations: {
- UPDATE_NUMBER (state, value) {
- state.number = value
- }
- },
- // 异步
- actions:{
- NUMBERACTION({state,commit},payload){
- return new Promise((resolve,reject)=>{
- commit('UPDATE_NUMBER',payload)
- console.log(111,state.number) // 想获取state的数据
- resolve() // 成功回调
- // reject() // catch回调
- })
- }
- },
modules:{
+ A,
+ B
}
})
export default store
使用modules:src/views/Home/index.vue
<template>
<div>这是Home组件</div>
<div>这是Store的数据:{{number}}</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
setTimeout(() => {
+ store.commit('A/UPDATE_NUMBER',50)
// store.commit('UPDATE_NUMBER',50)
M store.dispatch('A/NUMBERACTION',200).then(response => {
M console.log(response)
M }).catch((error)=>{
M console.log(error)
M })
M // const result = store.getters['getNumber']
M // console.log('result',result)
}, 2000)
const number = computed(() => store.state.number)
</script>
Vite 配置 stylus
安装依赖
npm install -D stylus-loader stylus
新建stylus变量:src/assets/style/stylConfig.styl
+ $red = red
根目录新建vite.config.js
+ import { defineConfig } from 'vite'
+ import vue from '@vitejs/plugin-vue'
+ import path from 'path'
+ // https://vitejs.dev/config/
+ export default defineConfig(({ command, mode }) => {
+ return {
+ plugins: [vue()],
+ css: {
+ preprocessorOptions: {
+ stylus: {
+ imports: [path.resolve(__dirname, 'src/assets/style/stylConfig.styl')]
+ }
+ }
+ },
+ }
+ })
使用stylus:src/views/Home/index.vue
<template>
M <div class="red-home">这是Home组件</div>
<div>这是Store的数据:{{number}}</div>
</template>
<script setup>
import { computed } from 'vue'
import { useStore } from 'vuex'
const store = useStore()
setTimeout(() => {
store.commit('A/UPDATE_NUMBER',50)
// store.commit('UPDATE_NUMBER',50)
store.dispatch('A/NUMBERACTION',200).then(response => {
console.log(response)
}).catch((error)=>{
console.log(error)
})
// const result = store.getters['getNumber']
// console.log('result',result)
}, 2000)
const number = computed(() => store.state.number)
</script>
+ <style lang="stylus">
+ .red-home
+ color $red
+ </style>
Vite配置路径别名
引入配置根目录vite.config.js:
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
return {
plugins: [vue()],
css: {
preprocessorOptions: {
stylus: {
imports: [path.resolve(__dirname, 'src/assets/style/stylConfig.styl')]
}
}
},
+ resolve: {
+ alias: {
+ '@': path.resolve(__dirname, 'src')
+ }
+ }
}
})
Vite esLint
初始化esLint
// 安装
npm install eslint -D
// 初始化配置
npx eslint --init
// to check syntax, find problems, and enforce code style
// JavaScript modules (import/export)
// Vue.js
// No Use TypeScript
// Browser Node
// Use a popular style guide
// Standard
// JavaScript
// install them now? Yes
// use npm
// 执行完之后,会在根目录生成 .eslintrc.cjs 文件
自动生成的.eslintrc.cjs
+ module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ node: true
+ },
+ extends: [
+ 'standard',
+ 'plugin:vue/vue3-essential'
+ ],
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ ecmaFeatyres: {
+ modules: true
+ },
+ requireConfigFile: false,
+ parser: '@babel/eslint-parser'
+ },
+ plugins: [
+ 'vue'
+ ],
+ rules: {
+ }
+ }
自动格式化esLint
// 安装
npm install vite-plugin-eslint @babel/eslint-parser -D
配置vite.config.js文件
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
+ import eslintPlugin from 'vite-plugin-eslint'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
return {
plugins: [
vue(),
+ // 添加下面这块
+ eslintPlugin({
+ include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue']
+ })
],
css: {
preprocessorOptions: {
stylus: {
imports: [path.resolve(__dirname, 'src/assets/style/stylConfig.styl')]
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
}
}
})
.eslintrc.cjs 添加规则
// 规则:http://eslint.cn/docs/rules/
+ module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ node: true
+ },
+ extends: [
+ 'standard',
+ 'plugin:vue/vue3-essential'
+ ],
+ parserOptions: {
+ ecmaVersion: 'latest',
+ sourceType: 'module',
+ ecmaFeatyres: {
+ modules: true
+ },
+ requireConfigFile: false,
+ parser: '@babel/eslint-parser'
+ },
+ plugins: [
+ 'vue'
+ ],
+ rules: {
+ semi: [2, 'never'], // 禁止尾部使用分号“;”
+ 'no-var': 'error', // 禁止使用 var
+ indent: ['error', 2], // 缩进2格
+ 'no-mixed-spaces-and-tabs': 'error', // 不能空格与tab混用
+ quotes: [2, 'single'], // 使用单引号
+ 'vue/html-closing-bracket-newline': 'off', // 不强制换行,
+ 'vue/singleline-html-element-content-newline': 'off', // 不强制换行
+ 'vue/multi-word-component-names': 'off',
+ 'vue/max-attributes-per-line': ['error', {
+ singleline: { max: 5 },
+ multiline: { max: 5 }
+ }]
+ }
+ }
vscode配置:文件→首选项→设置→settings.json
{
+ "[vue]": {
+ "editor.defaultFormatter": "octref.vetur"
+ },
+ "eslint.options":{ // 指定vscode的eslint所处理的文件的后缀
+ "extensions":[
+ ".js",
+ ".vue",
+ ".ts",
+ ".tsx"
+ ]
+ },
+ // 自动修复
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": true
+ }
}
Vite 配置跨域Proxy
本地环境跨域
server:{
proxy:{
'/api':{
target: 'xxxxx', // 代理的目标地址
changeOrigin: true,
secure: true, // 是否https接口
ws: true, // 是否代理websockets
rewrite: (path) => path.replace(/^\/api/,'')
}
}
}
初始化axios
// 安装
npm install axios -S
删减:src/views/Home/index.vue
<template>
<div class="red-home">这是Home组件</div>
<div>这是Store的数据:{{number}}</div>
</template>
<script setup>
- import { computed } from 'vue'
- import { useStore } from 'vuex'
- const store = useStore()
- setTimeout(() => {
- store.commit('A/UPDATE_NUMBER',50)
- store.dispatch('A/NUMBERACTION',200).then(response => {
- console.log(response)
- }).catch((error)=>{
- console.log(error)
- })
- }, 2000)
- const number = computed(() => store.state.number)
</script>
<style lang="stylus">
.red-home
color $red
</style>
关于旧的全局挂在axios方式:
app.config.globalProperties
一个用于注册能够被应用内所有组件实例访问到的全局属性的对象。
这是对 Vue 2 中 Vue.prototype 使用方式的一种替代,此写法在 Vue 3 已经不存在了。
与任何全局的东西一样,应该谨慎使用。
由此可知,vue3其实并不支持这种写法,但是也可以用,这里只做知识拓展,不建议使用
main.js:
import { createApp } from 'vue'
import App from './App.vue'
import router from './router'
import store from './store'
+ import axios from 'axios'
const app = createApp(App)
// 全局载入
+ app.config.globalProperties.$_api = axios
app.use(router).use(store).mount('#app')
--------------------------------
src/views/Home/index.vue:
<script setup>
+ import { getCurrentInstance } from 'vue'
+ const { proxy } = getCurrentInstance()
+ proxy.$axios.post('/xxx)
</script>
了解同源策略
/**
* 同源策略
* 1.http\https
* 2.端口
* 3.www.baidu.com\mail.163.com
*/
跨域重定向:配置vite.config.js文件
// https://cn.vitejs.dev/config/server-options.html#server-proxy
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import eslintPlugin from 'vite-plugin-eslint'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
return {
plugins: [
vue(),
// 添加下面这块
eslintPlugin({
include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue']
})
],
css: {
preprocessorOptions: {
stylus: {
imports: [path.resolve(__dirname, 'src/assets/style/stylConfig.styl')]
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
+ server: {
+ proxy: {
+ /**
+ * 1、匹配接口地址前面的字符是 /api,如果成功便会进入重定向
+ * 2、接口组合,用target属性的地址拼接请求地址
+ */
+ '/api': {
+ // 前两个必填
+ target: 'http://xxx.cn',
+ changeOrigin: true,
+ // secure: true, // 是否https接口
+ // ws: true // 是否代理websockets
+ // rewrite: (path) => path.replace(/^\/api/, '') // 匹配开头为/api 替换为空
+ }
+ }
+ },
}
})
Vite 配置环境变量
了解环境变量
开发项目时环境主要分为:
1、本地环境 == 在电脑开发项目
2、开发环境 == 部署在服务器环境(可以是,公司内部搭建的服务)// 192.168.110.184:8000
3、测试环境 == 部署在服务器环境(可以是,公司内部搭建的服务)// 192.168.110.184:8001
4、生产环境 == 部署在服务器环境,已经是对外公布的环境,可通过正式域名访问的环境
5、灰色环境 == 比较小型数据应用(公司内部测试,也是属于生产环境的一种)
参考文档:Vite 官方中文文档-环境变量和模式
Vite 在一个特殊的 import.meta.env
对象上暴露环境变量:
// 通过内建变量来判断运行环境
console.log('import.meta.env.MODE', import.meta.env.MODE)
console.log('import.meta.env.PROD', import.meta.env.PROD)
console.log('import.meta.env.DEV', import.meta.env.DEV)
console.log('import.meta.env.SSR', import.meta.env.SSR)
环境变量定义
.env文件:
运行 npm run dev ,会自动读取 .env.development 文件
运行 npm run build ,会自动读取 .env.production 文件
tips:为了防止意外地将一些环境变量泄漏到客户端,只有以VITE_
为前缀的变量才会暴露给经过 vite 处理的代码:
VITE_SOME_KEY=123
DB_PASSWORD=foobar
只有VITE_SOME_KEY
会被暴露为import.meta.env.VITE_SOME_KEY
提供给客户端源码,而DB_PASSWORD
则不会。
根目录新建.env.development文件
+ VITE_API = http://xx.dev.cn
根目录新建.env.production文件
+ VITE_API = http://xx.pro.cn
配置不同环境的变量
在某些情况下,若想在vite build
时运行不同的模式来渲染不同的标题,你可以通过传递--mode
选项标志来覆盖命令使用的默认模式。例如,如果你想在 staging (预发布)模式下构建应用:
vite build --mode staging
还需要新建一个 .env.staging 文件:
# .env.staging
VITE_APP_TITLE=My App (staging)
模式
在某些情况下,若想在vite build
时运行不同的模式来渲染不同的标题,你可以通过传递--mode
选项标志来覆盖命令使用的默认模式。
1、本地环境 npm run dev
2、开发环境 package.json: "build:dev" npm run build --mode dev
3、测试环境 package.json: "build:test" npm run build --mode test
4、生产环境 npm run build 自动读取.env.production
5、灰度环境
--mode dev 需要新建一个 .env.dev 文件:
+ VITE_API = 192.168.110.184:8000
--mode test需要新建一个 .env.test 文件:
+ VITE_API = 192.168.110.184:8001
在vite.config.js中应用:
// https://cn.vitejs.dev/config/server-options.html#server-proxy
M import { defineConfig,loadEnv } from 'vite'
import vue from '@vitejs/plugin-vue'
import path from 'path'
import eslintPlugin from 'vite-plugin-eslint'
// https://vitejs.dev/config/
export default defineConfig(({ command, mode }) => {
+ // 根据当前工作目录中的 `mode` 加载 .env 文件
+ // 设置第三个参数为 '' 来加载所有环境变量,而不管是否有 `VITE_` 前缀。
+ // js ts 文件需要引入loadEnv
+ // .vue 文件直接用 import.meta.env.DEV
+ const env = loadEnv(mode, process.cwd(), '')
+ console.log('env', env)
return {
plugins: [
vue(),
// 添加下面这块
eslintPlugin({
include: ['src/**/*.js', 'src/**/*.vue', 'src/*.js', 'src/*.vue']
})
],
css: {
preprocessorOptions: {
stylus: {
imports: [path.resolve(__dirname, 'src/assets/style/stylConfig.styl')]
}
}
},
resolve: {
alias: {
'@': path.resolve(__dirname, 'src')
}
},
server: {
proxy: {
/**
* 1、匹配接口地址前面的字符是 /api,如果成功便会进入重定向
* 2、接口组合,用target属性的地址拼接请求地址
*/
'/api': {
// 前两个必填
M target: env.VITE_API, // 192.168.110.184:8000/8001
changeOrigin: true,
// secure: true, // 是否https接口
// ws: true // 是否代理websockets
// rewrite: (path) => path.replace(/^\/api/, '') // 匹配开头为/api 替换为空
}
}
},
}
})