一、使用 vite 新建 Vue3 应用
Vite requires Node.js version >=12.0.0.
创建 vue3 项目
使用 @vue/cli 创建 vue2/3 项目
使用 vite 创建 vue3 项目
1 2 3 4 5 6 7 8 9 10 11 npm init vite-app xxxx yarn create vite-app xxxx cd xxxxnpm install yarn install npm run dev yarn dev
进入 vue3 项目
基本命令
1 2 3 yarn install //安装依赖 yarn dev //启动 yarn run build //发行
其他问题:
在执行 import App from './App.vue'
时,会提示找不到模块“./App.vue”
解决办法是在项目目录(src 同级)下添加 vue-shims.d.ts
文件,内容如下
1 2 3 4 declare module '*.vue' { import Vue from 'vue' ; export default Vue }
二、安装 vue-router 和 vuex
1 yarn add vue-router@next vuex@next
在 src 目录下新建 router/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 import { createRouter, createWebHistory, Router, RouteRecordRaw } from 'vue-router' const routes: Array <RouteRecordRaw> = [ { path :'/' , name :'Hash' , component :()=> import ('@view/Login.vue' ) } ] const router = createRouter({ history : createWebHistory('' ), routes, scrollBehavior (to, from , savedPosition ) { return { el : '#app' , top : 0 , behavior : 'smooth' , } }, }) export default router
在 src 目录下新建 store/index.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 import { InjectionKey } from 'vue' import { createStore, Store } from 'vuex' export interface State { count : number } export const key: InjectionKey<Store<State>> = Symbol ()export const store = createStore<State>({ state ( ) { return { count : 0 } }, mutations : { increment (state : State ) { state.count++ } } })
三、CSS 预处理器
安装最新版less
1 yarn add less less-loader --dev
兼容 vue2 的版本(vue2 无法使用最新版 less)
1 yarn add less less-loader@5 --save-dev
四、配置路径别名
1 yarn add @types/node --dev
vite.config.ts
配置如下
1 2 3 4 5 6 7 8 9 10 import { defineConfig } from 'vite' import path from 'path' export default defineConfig({... resolve :{ alias :{ "@" :path.resolve(__dirname,"./src" ) } } })
别名配置后,你会发现在 import .ts 后缀的文件时会有红色波浪线
An import path cannot end with a '.ts' extension.
原来是 import ts
时,需要在 tsconfig.json
配置别名,此处配置的别名需在 vite.config.ts
中有相同别名对应存在,具体配置如下:
tsconfig.json
1 2 3 4 5 6 7 8 "compilerOptions" : { "paths" : { "@/*" : ["src/*" ], "@view/*" :["src/views/*" ], "@utils/*" :["src/utils/*" ], "@api/*" :["src/api/*" ] } }
vite.config.ts
1 2 3 4 5 6 7 8 9 10 11 12 resolve:{ alias :{ "@" : path.resolve(__dirname, "./src" ), "@css" : path.resolve(__dirname, "./src/assets/css" ), "@c" : path.resolve(__dirname, "./src/components" ), "@img" : path.resolve(__dirname, "./src/assets/img" ), "@style" : path.resolve(__dirname, "./src/style" ), "@view" : path.resolve(__dirname, "./src/views" ), "@utils" :path.resolve(__dirname, "./src/utils" ), "@api" :path.resolve(__dirname, "./src/api" ), } },
五、配置服务器
vite.config.ts
配置如下
1 2 3 4 5 6 7 8 9 10 11 12 13 14 export default defineConfig({... server :{ port :3000 , proxy :{ '/api' :{ target :"http://localhost:8080" , changeOrigin :true , rewrite : path => path.replace(/^\/api/ , '' ) } } } ... })
说明
target
是你需要代理的地址,比如你的请求地址是
http://localhost:8080/product/list
那么 target 里面应该这么写:
target:'http://localhost:8080/'
编写请求的地方:
1 2 3 4 5 6 7 8 import { request } from '@/utils/request' function getProductList (parameter ) { return request({ method :'get' , url :'api/product/list' , parameter :parameter }) }
上述配置会将你的 url 去掉 api 然后拼接上 target 去请求服务器
六、Vue3 模板
点击 vscode 左下角的齿轮,选择 User Snippets->New Global Snippets file
复制下列模板代码进文件
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 { "Print to console" : { "prefix" : "vue" , "body" : [ "<!-- $0 -->" , "<template>" , " <div></div>" , "</template>" , "" , "<script lang='ts'>" , "import { defineComponent, ref, reactive, toRefs, onBeforeMount, onMounted} from 'vue'" , "interface DataProps {}" , "export default {" , " name: ''," , " setup() {" , " console.log('1-开始创建组件-setup')" , " const data: DataProps = reactive({" , "" , " })" , " onBeforeMount(() => {" , " console.log('2.组件挂载页面之前执行----onBeforeMount')" , " })" , " onMounted(() => {" , " console.log('3.-组件挂载到页面之后执行-------onMounted')" , " })" , " const refData = toRefs(data);" , " return {" , " ...refData," , " }" , "" , " }" , "};" , "</script>" , "<style lang='less' scoped>" , "</style>" , ], "description" : "Log output to console" } }
七、配置 env
vite 中的 env 配置,文档中有讲,这里从简总结。
首先在项目根目录(src同级)处新建 .env.production
和 .env.development
,然后用 key=value
的方式填写你需要使用的环境变量,换行区分。
在两个文件中分别配置 NODE_ENV='development'
和 NODE_ENV='production'
。vite 会根据你使用 package.json
中的哪个 script,来确定项目的启动方式 MODE,然后通过 MODE 选择加载对应 .env.[mode]
文件的环境变量。让我们可以通过全局变量 import.meta.env.MODE
获取当前项目的 MODE
我们通常不会把配置变量写在 .env.[mode]
文件中,而是在 src
新建 config
文件夹,里面创建 index.ts
存储变量,后面再使用 import.meta.env.MODE
导出对应的配置变量对象。
八、配置 axios
网传 ts 配置 axios 有两种方式
第一种
建一个 axios.ts
放在 utils
文件夹,在其中创建 axios 实例,配置拦截器,然后直接 export 出一个 axios 实例。这种方式的配置,可以直接在 api.ts
中导入实例直接使用。
第二种
也是建一个 axios.ts
放在 utils
文件夹,不同的是 export 出的并不是一个 axios 实例,而是一个带 init 方法的 axios 自定义类。
这里介绍第一种:
utils
文件夹的 axios.ts
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 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 import axios, { AxiosResponse, AxiosRequestConfig } from "axios" ;import { reactive } from '@vue/reactivity' import { ElMessage } from 'element-plus' import { config } from "@/config" ;const service = axios.create({ baseURL : config.baseApi, timeout : 5000 , withCredentials : false }) service.interceptors.request.use( (config: AxiosRequestConfig ) => { if (config.loading) { } return config; }, (error: any ) => { Promise .reject(error); } ) service.interceptors.response.use( async (response: AxiosResponse) => { const res = response.data; if (res.code !== 0 ) { if (res.code === 401 ){ ElMessage(res.message) return ; } if (res.code == 403 ) { ElMessage(res.message) return } return Promise .reject(new Error (res.msg || "Error" )) } else return response.data }, (error: any ) => { if (error && error.response) { switch (error.response.status) { case 400 : error.message = "请求错误(400)" break case 401 : error.message = "未授权,请登录(401)" break case 403 : error.message = "拒绝访问(403)" break case 404 : error.message = `请求地址出错: ${error.response.config.url} ` break case 405 : error.message = "请求方法未允许(405)" break case 408 : error.message = "请求超时(408)" break case 500 : error.message = "服务器内部错误(500)" break case 501 : error.message = "服务未实现(501)" break case 502 : error.message = "网络错误(502)" break case 503 : error.message = "服务不可用(503)" break case 504 : error.message = "网络超时(504)" break case 505 : error.message = "HTTP版本不受支持(505)" break default : error.message = `连接错误: ${error.message} ` } } else { if (error.message == "Network Error" ) { error.message = "网络异常,请检查后重试!连接到服务器失败,请联系管理员" } } ElMessage(error.message) return Promise .reject(error) } ) function request (config:AxiosRequestConfig ) { let result = reactive({}) service(config).then(response => { Object .assign(result,response.data) }).catch(e => { console .error(e) }) return result } export { service, request }
在 api 文件中使用配置好的 axios
api
文件夹的 article.ts
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 import { request } from '@/utils/axios' ; export function article (parameter: any ) { return request({ url :'/api/article' , method :'get' , params :parameter }) } export function author (parameter: any ) { return request({ url :'/api/article/author' , method :'get' , params :parameter }) } export function commentList (parameter: any ) { return request({ url :'/api/article/commentList' , method :'get' , params :parameter }) }
这里还导出了一个 request 函数,因为 vue3 有 reactive,即使异步请求之前返回了空的 result,后续回调依然可以给这个已经返回的 result 赋值。这样我们就可以在 component 中这样获取值。
1 2 3 4 5 6 7 8 9 setup ( ) { const data = reactive({ article :article({}), author :author({}), commentList :commentList({}), }) const refData = toRefs(data); return { ...refData } }
九、配置 mock
1 2 yarn add mockjs yarn add vite-plugin-mock -D
新建 mock 目录(src同级),在 mock 目录中新建 test.ts 作为 mock 数据文件
mock/test.ts
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 import { MockMethod } from 'vite-plugin-mock' export default [ { url :'/api/article' , method :'get' , response :() => { return { code :0 , data :{ id :'1' , category :'人文' , body :'halo' , recommendCount :(Math .random()*100 ).toFixed(0 ), againstCount :(Math .random()*100 ).toFixed(0 ), pre :'NOR Flash驱动程序' , next :'移植DM900C网卡驱动' , commentCount :(Math .random()*100 ).toFixed(0 ), visitCount :(Math .random()*100 ).toFixed(0 ), } } } }, { url :'/api/article/author' , method :'get' , response :() => { return { code :0 , data :{ id :'1' , name :'quinoa' , followCount :(Math .random()*100 ).toFixed(0 ), fansCount :(Math .random()*100 ).toFixed(0 ), } } } } ] as MockMethod[]
在 utils
目录新建 mockProdServer.ts
文件
1 2 3 4 5 6 7 8 9 10 import { createProdMockServer } from 'vite-plugin-mock/es/createProdMockServer' ;import testModule from 'mock/test' ;export function setupProdMockServer ( ) { createProdMockServer([...testModule]); }
在 vite.config.ts
中配置 vite-plugin-mock
vite.config.ts
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 plugins: [ vue(), viteMockServe({ mockPath :'./mock' , supportTs :true , watchFiles :true , localEnabled :true , prodEnabled :command !=='serve' && prodMock, injectCode :` import { setupProdMockServer } from '@utils/mockProdServer'; setupProdMockServer(); ` }) ]
在 vite.config.ts 中关闭代理服务器,就可以使用 ts 文件模拟的 mock 数据了。
需要注意的是如果启动项目时出错
1 2 3 9 :30 :31 ├F10: AM┤ [vite:mock] mock reload error Error [ERR_STREAM_DESTROYED]: Cannot call write after a stream was destroyedevents.js:292 throw er;
这是目前可能会遇到的 esbuild
的 bug(2021-4至2021-7),在当前 blog 项目中使用如下命令可解决
1 node ./node_modules/esbuild/install.js