本文章根据coderwhy老师课程整理,结合视频食用更佳深入Vue3+TypeScript技术栈-coderwhy大神新课-学习视频教程-腾讯课堂
一、vue.config.js配置
vue.config.js有三种配置方式:三选一即可
* 方式一:直接通过CLI提供给我们的选项来配置:
比如publicPath:配置应用程序部署的子目录(默认是 `/`,相当于部署在 `https://www.my-app.com/`);
比如outputDir:修改输出的文件夹;
* 方式二:通过configureWebpack修改webpack的配置:
可以是一个对象,直接会被合并;
可以是一个函数,会接收一个config,可以通过config来修改配置;
* 方式三:通过chainWebpack修改webpack的配置:
是一个函数,会接收一个基于webpack-chain的config对象,可以对配置进行修改;
根目录下创建vue.config.js文件
//方式二的依赖path模块
const path = require('path')
module.exports = {
//方式一:
// outputDir: './build',
//方式二:里面的内容最终会在webpack中合并
// configureWebpack: {
// resolve: {
// alias: {
// views: '@/views',
// components: '@/components'
// }
// }
// },
// configureWebpack: (config) => {
// config.resolve.alias = {
// '@': path.resolve(__dirname, 'src'),
// views: '@/views'
// }
// },
//方式三:
chainWebpack: (config) => {
config.resolve.alias
.set('@', path.resolve(__dirname, 'src'))
.set('views', '@/views')
}
}
二、vue-router集成
安装vue-router的最新版本:
npm install vue-router@next
创建router对象:
import { createRouter, createWebHashHistory } from 'vue-router'
import type { RouteRecordRaw } from 'vue-router'
const routes: RouteRecordRaw[] = [
{
path: '/',
redirect: '/main'
},
{
path: '/main',
component: () => import('../views/main/main.vue')
},
{
path: '/login',
component: () => import('../views/login/login.vue')
}
]
const router = createRouter({
routes,
history: createWebHashHistory()
})
export default router
main.ts中安装router:
import router from './router'
createApp(App).use(router).mount('#app')
在App.vue中配置跳转:
<template>
<div id="app">
<router-link to="/login">登录</router-link>
<router-link to="/main">首页</router-link>
<router-view></router-view>
</div>
</template>
三、vuex集成
安装vuex:
npm install vuex@next
创建store对象:
import { createStore } from 'vuex'
const store = createStore({
state() {
return {
name: 'coderwhy'
}
},
mutations: {},
getters: {},
actions: {},
modules: {}
})
export default store
main.ts中安装store:
import store from './store'
createApp(App).use(router).use(store).mount('#app')
在App.vue中使用:
<h2>{{ $store.state.name }}</h2>
四、element-plus集成
Element Plus,一套为开发者、设计师和产品经理准备的基于 Vue 3.0 的桌面端组件库:
* 在Vue2中都使用过element-ui,而element-plus正是element-ui针对于vue3开发的一个UI组件库;
* 它的使用方式和很多其他的组件库是一样的,所以学会element-plus,其他类似于ant-design-vue、NaiveUI、VantUI都是差不多的;
安装element-plus
npm install element-plus
1. 全局引入
一种引入element-plus的方式是全局引入,代表的含义是所有的组件和插件都会被自动注册:
import ElementPlus from 'element-plus'
import 'element-plus/lib/theme-chalk/index.css'//引用组件库的样式
import router from './router'
import store from './store'
createApp(App).use(router).use(store).use(ElementPlus).mount('#app')
2. 局部引入
也就是在开发中用到某个组件对某个组件进行引入:
<template>
<div id="app">
<router-link to="/login">登录</router-link>
<router-link to="/main">首页</router-link>
<router-view></router-view>
<h2>{{ $store.state.name }}</h2>
<el-button>默认按钮</el-button>
<el-button type="primary">主要按钮</el-button>
<el-button type="success">成功按钮</el-button>
<el-button type="info">信息按钮</el-button>
<el-button type="warning">警告按钮</el-button>
<el-button type="danger">危险按钮</el-button>
</div>
</template>
<script lang="ts">
import { defineComponent } from 'vue'
import { ElButton } from 'element-plus'
export default defineComponent({
name: 'App',
components: {
ElButton
}
})
</script>
<style lang="less">
</style>
但是我们会发现是没有对应的样式的,引入样式有两种方式:
* 全局引用样式(像之前做的那样);
* 局部引用样式(通过babel的插件);
1.安装babel的插件:
npm install babel-plugin-import -D
2.配置babel.config.js
module.exports = {
plugins: [
[
'import',
{
libraryName: 'element-plus',
customStyleName: (name) => {
return `element-plus/lib/theme-chalk/${name}.css`
}
}
]
],
presets: ['@vue/cli-plugin-babel/preset']
}
但是这里依然有个弊端:
* 这些组件我们在多个页面或者组件中使用的时候,都需要导入并且在components中进行注册;
* 所以我们可以将它们在全局注册一次;
可以封装起来。根目录下建global文件夹,下建index.ts放全局的注册
import 'element-plus/lib/theme-chalk/base.css'
import {
ElButton,
ElTable,
ElAlert,
ElAside,
ElAutocomplete,
ElAvatar,
ElBacktop,
ElBadge,
} from 'element-plus'
const components = [
ElButton,
ElTable,
ElAlert,
ElAside,
ElAutocomplete,
ElAvatar,
ElBacktop,
ElBadge
]
export function registerApp (app) {
for (const cpn of components) {
app.component(cpn.name, cpn)
}
}
在main.ts中引用
import { registerApp } from '../global'
const app = createApp(App)
registerApp(app)
五、axios集成
axios安装
npm install axios
可全局配置
全局的配置
axios.defaults.baseURL = 'http://httpbin.org'
axios.defaults.timeout = 10000
可局部配置,即每一个单独配置
axios
.get('/get', {
params: {
name: 'coderwhy',
age: 18
},
timeout: 5000,
headers: {}
})
.then((res) => {
console.log(res.data)
})
多个请求一起返回
axios.all -> 多个请求, 一起返回
axios
.all([
axios.get('/get', { params: { name: 'why', age: 18 } }),
axios.post('/post', { data: { name: 'why', age: 18 } })
])
.then((res) => {
console.log(res[0].data)
console.log(res[1].data)
})
axios的拦截器
axios 的拦截器:interceptors
如果我们想在请求之前做点什么,用拦截器再好不过了
拦截器一般做什么?
1. 修改请求头的一些配置项
2. 给请求的过程添加一些请求的图标
3. 给请求添加参数
1. 全局的拦截器配置
// fn1: 请求发送成功会执行的函数
// fn2: 请求发送失败会执行的函数
axios.interceptors.request.use(
(config) => {
// 想做的一些操作
// 1.给请求添加token
// 2.isLoading动画
console.log('请求成功的拦截')
return config
},
(err) => {
console.log('请求发送错误')
return err
}
)
// fn1: 数据响应成功(服务器正常的返回了数据 20x)
axios.interceptors.response.use(
(res) => {
console.log('响应成功的拦截')
return res
},
(err) => {
console.log('服务器响应失败')
return err
}
)
2. 局部的拦截器
let instance = axios.create({
baseURL:"./json/",
timeOut:5000
});
instance.interceptors.request.use(config=>{
console.log(1234);
return config
},err=>{
console.log(err)
})
instance({
url:"/01.json",
method:"get"
}).then(res=>{
console.log(res)
})
六、区分不同环境
在开发时,我们可能需要根据不同的环境设置环境变量
开发环境: development
生成环境: production
测试环境: test
常见方式有三种:
1.手动修改不同的变量(不推荐)
2.根据peocess.env.NODE_ENV的值来区分(用的比较多)
3.编写不同的环境变量配置文件(也推荐)
一、根据peocess.env.NODE_ENV的值来区分
创建config.ts文件
// 开发环境: development
// 生成环境: production
// 测试环境: test
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = 'http://coderwhy.org/prod'
} else {
BASE_URL = 'http://coderwhy.org/test'
}
export { BASE_URL, TIME_OUT }
二、编写不同的环境变量配置文件
在文件中配置对应的参数
VUE_APP_BASE_URL=https://coderwhy.org/dev
VUE_APP_BASE_NAME=coderwhy
在其他文件中调用时
console.log(process.env.VUE_APP_BASE_URL)
console.log(process.env.VUE_APP_BASE_NAME)
六、axios+ts请求库封装
思路:创建service目录
service
request
--config.ts
--index.ts
--type.ts
--index.ts
在request的index.ts中封装导出,封装是封装到类里,比function封装性更强
import axios from 'axios'
class HYRequest {
//封装方法。到时候hyRequest.request等调用
request() {}
get() {}
}
export default HYRequest
在service下index.ts中封装导出
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'
const hyRequest = new HYRequest({
})
export default hyRequest
在main.ts中使用
import hyRequest from './service'
hyRequest.request({
url: '/home/multidata',
method: 'GET',
headers: {},
interceptors: {
requestInterceptor: (config) => {
console.log('单独请求的config')
config.headers['token'] = '123'
return config
},
responseInterceptor: (res) => {
console.log('单独响应的response')
return res
}
}
})
在request下index.ts中封装类calss HYRequest,可以创建多个实例,如果有多个baseURL的时候,就可以创建多个实例,每个实例有自己的baseURL,大多数项目是向一个服务器请求,就一个实例对象就行
可给实例创建拦截器,拦截器的封装可分为给全局、单个实例、单个请求添加拦截器(用的比较少),如果觉得复杂,也可只给全局添加封装拦截器
最终封装
service
request
--config.ts
--index.ts
--type.ts
--index.ts
request--type.ts
import type { AxiosRequestConfig, AxiosResponse } from 'axios'
export interface HYRequestInterceptors<T = AxiosResponse> {
requestInterceptor?: (config: AxiosRequestConfig) => AxiosRequestConfig
requestInterceptorCatch?: (error: any) => any
responseInterceptor?: (res: T) => T
responseInterceptorCatch?: (error: any) => any
}
export interface HYRequestConfig<T = AxiosResponse> extends AxiosRequestConfig {
interceptors?: HYRequestInterceptors<T>
showLoading?: boolean
}
request--config.ts
let BASE_URL = ''
const TIME_OUT = 10000
if (process.env.NODE_ENV === 'development') {
BASE_URL = 'http://123.207.32.32:8000/'
} else if (process.env.NODE_ENV === 'production') {
BASE_URL = 'http://coderwhy.org/prod'
} else {
BASE_URL = 'http://coderwhy.org/test'
}
export { BASE_URL, TIME_OUT }
request--index.ts
import axios from 'axios'
import type { AxiosInstance } from 'axios' //导入类型
import type { HYRequestInterceptors, HYRequestConfig } from './type'
import { ElLoading } from 'element-plus'
import { ILoadingInstance } from 'element-plus/lib/el-loading/src/loading.type'
const DEAFULT_LOADING = true
class HYRequest {
instance: AxiosInstance //类型注解
interceptors?: HYRequestInterceptors
showLoading: boolean
loading?: ILoadingInstance
//HYRequestConfig为了扩展类型
constructor(config: HYRequestConfig) {
// 创建axios实例
this.instance = axios.create(config)
// 保存基本信息
this.showLoading = config.showLoading ?? DEAFULT_LOADING
this.interceptors = config.interceptors
// 使用拦截器
// 1.从config中取出的拦截器是对应的实例的拦截器
this.instance.interceptors.request.use(
this.interceptors?.requestInterceptor,
this.interceptors?.requestInterceptorCatch
)
this.instance.interceptors.response.use(
this.interceptors?.responseInterceptor,
this.interceptors?.responseInterceptorCatch
)
// 2.添加所有的实例都有的拦截器(如果觉得复杂,就只封装这个全局的)
this.instance.interceptors.request.use(
(config) => {
console.log('所有的实例都有的拦截器: 请求成功拦截')
if (this.showLoading) {
this.loading = ElLoading.service({
lock: true,
text: '正在请求数据....',
background: 'rgba(0, 0, 0, 0.5)'
})
}
return config
},
(err) => {
console.log('所有的实例都有的拦截器: 请求失败拦截')
return err
}
)
this.instance.interceptors.response.use(
(res) => {
console.log('所有的实例都有的拦截器: 响应成功拦截')
// 将loading移除
this.loading?.close()
const data = res.data
if (data.returnCode === '-1001') {
console.log('请求失败~, 错误信息')
} else {
return data
}
},
(err) => {
console.log('所有的实例都有的拦截器: 响应失败拦截')
// 将loading移除
this.loading?.close()
// 例子: 判断不同的HttpErrorCode显示不同的错误信息
if (err.response.status === 404) {
console.log('404的错误~')
}
return err
}
)
}
request<T>(config: HYRequestConfig<T>): Promise<T> {
return new Promise((resolve, reject) => {
// 1.单个请求对请求config的处理
if (config.interceptors?.requestInterceptor) {
config = config.interceptors.requestInterceptor(config)
}
// 2.判断是否需要显示loading
if (config.showLoading === false) {
this.showLoading = config.showLoading
}
this.instance
.request<any, T>(config)
.then((res) => {
// 1.单个请求对数据的处理
if (config.interceptors?.responseInterceptor) {
res = config.interceptors.responseInterceptor(res)
}
// 2.将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
// 3.将结果resolve返回出去
resolve(res)
})
.catch((err) => {
// 将showLoading设置true, 这样不会影响下一个请求
this.showLoading = DEAFULT_LOADING
reject(err)
return err
})
})
}
get<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'GET' })
}
post<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'POST' })
}
delete<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'DELETE' })
}
patch<T>(config: HYRequestConfig<T>): Promise<T> {
return this.request<T>({ ...config, method: 'PATCH' })
}
}
export default HYRequest
service---index.ts
// service统一出口
import HYRequest from './request'
import { BASE_URL, TIME_OUT } from './request/config'
const hyRequest = new HYRequest({
baseURL: BASE_URL,
timeout: TIME_OUT,
interceptors: {
requestInterceptor: (config) => {
// 携带token的拦截
const token = ''
if (token) {
config.headers.Authorization = `Bearer ${token}`
}
console.log('请求成功的拦截')
return config
},
requestInterceptorCatch: (err) => {
console.log('请求失败的拦截')
return err
},
responseInterceptor: (res) => {
console.log('响应成功的拦截')
return res
},
responseInterceptorCatch: (err) => {
console.log('响应失败的拦截')
return err
}
}
})
export default hyRequest