vue3 と ts はどのように axios をカプセル化し、mock.js を使用しますか?

今日は、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. Axios 依存関係のインストールと処理

  1.依存関係のインストール

  2. グローバル axios パッケージ

  3. 実際の使用

2.mock.jsの依存インストールと処理

  1.依存関係をインストールする

  2. モックに必要なファイルを作成する

  1. 新しい mockjs/javaScript/index.ts (特定のデータ ファイル) を作成します。 

  2. 新しい mockjs/index.ts を作成します 

  3.main.ts インポート

3.併用する


1. Axios 依存関係のインストールと処理

  1.依存関係のインストール

        非同期ネットワーク リクエストの使用は、読み込み、メッセージ、その他のプロンプトと切り離すことができない必要があります。

// 安装axios 

npm install axios --save


// 安装 elementPlus

npm install element-plus --save

        

  2. グローバル axios パッケージ

 srcディレクトリ配下のutilsディレクトリ配下に新たにrequest.tsを作成 TSを利用するため、あらかじめデータフォーマットを定義しておく必要があります。

  1. 事前に確認が必要なリクエストデータの返送フォーマットを定義する
  2. axios の基本構成情報を定義する
  3. リクエスト インターセプター: すべてのリクエストが最初に到着する場所、リクエスト ヘッダー情報をカスタマイズできる場所 (トークン、多言語など)
  4. 応答インターセプター: データが最初に到着した場所を返します。ここで、例外情報を処理できます (例: コード 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 を追加します

  1. リクエストのパラメータ タイプを定義する
  2. 特定のパラメータ タイプへの応答を定義する

ここでは 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>

おすすめ

転載: blog.csdn.net/weixin_56650035/article/details/127467646