今日は、vue3+ts が axios をエレガントにカプセル化し、mock.js を組み合わせてアジャイル開発を実現する方法を見てみましょう。
ただし、Axios と Ajax の違いに注意する必要があります。
Ajax は、HTML または XHTML、CSS、JavaScript、DOM、XML、XSLT、およびブラウザーとサーバー間の非同期データ転送 (HTTP 要求) に使用される最も重要な XMLHttpRequest を含む技術の総称です。部分的な更新、使用は XMLHttpRequest に基づいています。
Axios は promise ベースの HTTP ライブラリであり、サードパーティのライブラリです
現在の主なテクノロジー スタック: vue3、ts、axios、mock.js、elementPlus
目次
1. 新しい mockjs/javaScript/index.ts (特定のデータ ファイル) を作成します。
1. Axios 依存関係のインストールと処理
1.依存関係のインストール
非同期ネットワーク リクエストの使用は、読み込み、メッセージ、その他のプロンプトと切り離すことができない必要があります。
// 安装axios
npm install axios --save
// 安装 elementPlus
npm install element-plus --save
2. グローバル axios パッケージ
srcディレクトリ配下のutilsディレクトリ配下に新たにrequest.tsを作成 TSを利用するため、あらかじめデータフォーマットを定義しておく必要があります。
- 事前に確認が必要なリクエストデータの返送フォーマットを定義する
- axios の基本構成情報を定義する
- リクエスト インターセプター: すべてのリクエストが最初に到着する場所、リクエスト ヘッダー情報をカスタマイズできる場所 (トークン、多言語など)
- 応答インターセプター: データが最初に到着した場所を返します。ここで、例外情報を処理できます (例: コード 401 はログインにリダイレクトし、コード 500 はエラー メッセージを表示します)。
import axios, { AxiosInstance, AxiosError, AxiosRequestConfig, AxiosResponse } from "axios";
import { ElMessage, ElLoading, ElMessageBox } from "element-plus";
// response interface { code, msg, success }
// 不含 data
interface Result {
code: number,
success: boolean,
msg: string
}
// request interface,包含 data
interface ResultData<T = any> extends Result {
data?: T
}
enum RequestEnums {
TIMEOUT = 10000, // 请求超时 request timeout
FAIL = 500, // 服务器异常 server error
LOGINTIMEOUT = 401, // 登录超时 login timeout
SUCCESS = 200, // 请求成功 request successfully
}
// axios 基础配置
const config = {
// 默认地址,可以使用 process Node内置的,项目根目录下新建 .env.development
baseURL: process.env.VUE_APP_BASE_API as string,
timeout: RequestEnums.TIMEOUT as number, // 请求超时时间
withCredentials: true, // 跨越的时候允许携带凭证
}
class Request {
service: AxiosInstance;
constructor(config: AxiosRequestConfig) {
// 实例化 serice
this.service = axios.create(config);
/**
* 请求拦截器
* request -> { 请求拦截器 } -> server
*/
this.service.interceptors.request.use(
(config: AxiosRequestConfig) => {
const token = localStorage.getItem('token') ?? '';
return {
...config,
headers: {
'customToken': "customBearer " + token
}
}
},
(error: AxiosError) => {
// 请求报错
Promise.reject(error)
}
);
/**
* 响应拦截器
* response -> { 响应拦截器 } -> client
*/
this.service.interceptors.response.use(
(response: AxiosResponse) => {
const { data, config } = response;
if (data.code === RequestEnums.LOGINTIMEOUT) {
// 表示登录过期,需要重定向至登录页面
ElMessageBox.alert("Session expired", "System info", {
confirmButtonText: 'Relogin',
type: 'warning'
}).then(() => {
// 或者调用 logout 方法去处理
localStorage.setItem('token', '');
location.href = '/'
})
}
if (data.code && data.code !== RequestEnums.SUCCESS) {
ElMessage.error(data);
return Promise.reject(data);
}
return data
},
(error: AxiosError) => {
const { response } = error;
if (response) {
this.handleCode(response.status);
}
if (!window.navigator.onLine) {
ElMessage.error("网络连接失败,请检查网络");
// 可以重定向至404页面
}
}
)
}
public handleCode = (code: number): void => {
switch (code) {
case 401:
ElMessage.error("登陆失败,请重新登录");
break;
case 500:
ElMessage.error("请求异常,请联系管理员");
break;
default:
ElMessage.error('请求失败');
break;
}
}
// 通用方法封装
get<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.get(url, { params });
}
post<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.post(url, params);
}
put<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.put(url, params);
}
delete<T>(url: string, params?: object): Promise<ResultData<T>> {
return this.service.delete(url, { params });
}
}
export default new Request(config)
3. 実際の使用
src ディレクトリの下に api/index.ts を追加します
- リクエストのパラメータ タイプを定義する
- 特定のパラメータ タイプへの応答を定義する
ここでは ts で名前空間を使用します. 実際の開発では、多くの API が同じ名前で意味が異なる場合があるため、名前空間を使用して定義します
import request from "@/utils/request";
namespace User {
// login
export interface LoginForm {
userName: string,
password: string
}
}
export namespace System {
export interface Info {
path: string,
routeName: string
}
export interface ResponseItem {
code: number,
items: Array<Sidebar>,
success: boolean
}
export interface Sidebar {
id: number,
hashId: string | number,
title: string,
routeName: string,
children: Array<SidebarItem>,
}
export interface SidebarItem {
id: number,
parentId: number,
hashId: string | number,
title: string,
}
}
export const info = (params: System.Info) => {
// response
if (!params || !params.path) throw new Error('Params and params in path can not empty!')
// 这里因为是全局的一个info,根据路由地址去请求侧边栏,所需不用把地址写死
return request.post<System.Sidebar>(params.path, { routeName: params.routeName })
}
Vue ファイルで呼び出される
<script lang="ts" setup name="Sidebar">
import { ref, reactive, onBeforeMount } from "vue"
import { info } from "@/api"
import { useRoute } from "vue-router"
const route = useRoute();
let loading = ref<boolean>(false);
let sidebar = ref<any>({});
const _fetch = async (): Promise<void> => {
const routeName = route.name as string;
const path = '/' + routeName.replace(routeName[0], routeName[0].toLocaleLowerCase()) + 'Info'
try {
loading.value = true;
const res = await info({ path, routeName });
if (!res || !res.data) return;
sidebar.value = res.data;
} finally {
loading.value = false
}
}
onBeforeMount(() => {
_fetch();
})
</script>
2.mock.jsの依存インストールと処理
1.依存関係をインストールする
# 安装
npm install mockjs --save
ts で使用する場合、モジュールを shims-vue.d.ts ファイルにスローする必要があります。そうしないと、エラーが発生するという問題が発生します。
/* eslint-disable */
declare module '*.vue' {
import type { DefineComponent } from 'vue'
const component: DefineComponent<{}, {}, any>
export default component
}
declare module 'mockjs';
2. モックに必要なファイルを作成する
index.ts (mockjs グローバル構成ファイルに属します)、mockjs/javaScript/index.ts (特定のデータ ファイル)、この 2 つは注意する必要があり、他は注意する必要はありません
1. 新しい mockjs/javaScript/index.ts (特定のデータ ファイル) を作成します。
ここのデータは主にサイドバーのデータで固定なのでmockjsのルールは使わずにデータを生成
import { GlobalSidebar, Sidebar } from "../../sidebar";
namespace InfoSidebar {
export type InfoSidebarParams = {
body: string,
type: string,
url: string
}
}
const dataSource: Array<GlobalSidebar> = [
{
mainTitle: 'JavaScript基础问题梳理',
mainSidebar: [
{
id: 0,
hashId: 'This',
title: 'this指向',
routeName: 'JsBasic',
children: [
{
id: 1,
parentId: 0,
hashId: 'GlobalFunction',
title: '全局函数'
},
{
id: 2,
parentId: 0,
hashId: 'ObjectMethod',
title: '对象方法'
},
{
id: 3,
parentId: 0,
hashId: 'Constructor',
title: '构造函数'
},
{
id: 4,
parentId: 0,
hashId: 'SetTimeout',
title: '定时器、回调函数'
},
{
id: 5,
parentId: 0,
hashId: 'EventFunction',
title: '事件函数'
},
{
id: 6,
parentId: 0,
hashId: 'ArrowFunction',
title: '箭头函数'
},
{
id: 7,
parentId: 0,
hashId: 'CallApplyBind',
title: 'call、apply、bind'
},
]
},
{
id: 2,
hashId: 'DeepClone',
title: '深拷贝和浅拷贝',
routeName: 'JsBasic',
children: []
}
]
},
];
export default {
name: 'jsBasicInfo',
jsBasicInfo(params: InfoSidebar.InfoSidebarParams) {
const param = JSON.parse(params.body)
if (!param) throw new Error("Params can not empty!");
const data = dataSource.find((t: GlobalSidebar) => {
return t.mainSidebar.filter((x: Sidebar) => {
return x.routeName === param.routeName
})
})
return {
data,
success: true,
code: 200
}
}
}
Sidebar.ts
/**
* @param { number } id Unique value
* @param { string } hashId href Unique value
* @param { string } title show current title
* @param { string } routeName page find data
*/
interface GlobalSidebar {
mainTitle: string,
mainSidebar: Array<Sidebar>
}
interface Sidebar {
id: number,
hashId: string | number,
title: string,
routeName: string,
children: Array<SidebarItem>,
}
interface SidebarItem {
id: number,
parentId: number,
hashId: string | number,
title: string,
}
export {
GlobalSidebar,
Sidebar,
SidebarItem
}
2. 新しい mockjs/index.ts を作成します
import Mock from "mockjs";
import jsBasicInfo from "./tpl/javaScript/index";
const requestMethod = 'post';
const BASE_URL = process.env.VUE_APP_BASE_API;
const mocks = [jsBasicInfo];
for (let i of mocks) {
Mock.mock(BASE_URL + '/' + i.name, requestMethod, i.jsBasicInfo);
}
export default Mock
3.main.ts インポート
import { createApp } from 'vue'
import App from './App.vue'
if(process.env.NODE_ENV == 'development'){
require('./mockjs/index')
}
const app = createApp(App);
app.mount('#app');
3.併用する
実際、axios を呼び出したのはコードの一部です。
<script lang="ts" setup name="Sidebar">
import { ref, reactive, onBeforeMount } from "vue"
import { info } from "@/api"
import { useRoute } from "vue-router"
const route = useRoute();
let loading = ref<boolean>(false);
let sidebar = ref<any>({});
const _fetch = async (): Promise<void> => {
const routeName = route.name as string;
const path = '/' + routeName.replace(routeName[0], routeName[0].toLocaleLowerCase()) + 'Info'
try {
loading.value = true;
const res = await info({ path, routeName });
if (!res || !res.data) return;
sidebar.value = res.data;
} finally {
loading.value = false
}
}
onBeforeMount(() => {
_fetch();
})
</script>