ジェネリックスを使用して、API メソッドとパラメーターを自動的に要求する関数を作成する方法

ファイル

この記事は最初に公開されました: https://github.com/bigo-frontend/blog/ フォローと再投稿を歓迎します。

ジェネリックスを使用して、API メソッドとパラメーターを自動的に要求する関数を作成する方法

最近、ts を使用して Vue アプリケーションを開発しているのですが、その開発過程でジェネリックの概念に出会いました。ジェネリックスの理解と理解に基づいて、ジェネリックスの特性を使用して
API 自動プロンプト機能を実装できれば、同じプロジェクト内の他の開発者にリマインドするだけでなく、確認の手間も​​省けるのではないかと思いつきました。ドキュメント化の取り組み。この一連のメソッドを当社の TypeScript プロジェクト テンプレートに組み込むこともできます。これは、他の同僚が開発して使用するのに便利で、会社の研究開発効率を向上させることができます。やってみましょう、その方法について話しましょう。
まず、いくつかの概念を理解する必要があります

ジェネリック

まずはコードを見てみましょう

class Stack {
    
    
  private data = [];
  pop () {
    
    
    return this.data.pop()
  }
  push (item) {
    
    
    this.data.push(item)
  }
}
const stack = new Stack();
stack.push(1);
stack.push('string');
stack.pop();

上記は先入れ後出しスタックの JavaScript 実装です。呼び出すとき、データは任意の型にすることができます。ただし、typescript を使用して実装する場合は、次のように指定された型を渡す必要があります。

class Stack {
    
    
  private data:number = [];
  pop (): number {
    
    
    return this.data.pop();
  }
  push (item: number) {
    
    
    this.data.push(item);
  }
}
const stack = new Stack();
stack.push(1);
stack.push('string'); // Error: type error

上記のコードでは、スタックにプッシュおよびポップされる要素が数値型であることを指定していますが、非数値型をスタックにプッシュすると、typescript コンパイラはエラーを報告します。ただし、実装するクラスは複数の場所で使用されることが多く、それに含まれる要素の型も異なる場合があります。それでは、この時点でこの Stack クラスをさまざまな場所で呼び出すと、内部のデータ要素が目的のタイプになるようにするにはどうすればよいでしょうか。以下のコードを見てください

class Stack<T> {
    
    
  private data:T = [];
  pop (): T {
    
    
    return this.data.pop();
  }
  push (item: T) {
    
    
    this.data.push(item);
  }
}
const stack = new Stack<string>();
stack.push(1); // Error: type error

上記のコードでは、山かっこを Stack クラスに追加し、それに T を渡しました。この T はジェネリック型で、呼び出し時にクラスがさまざまな型を渡すことができることを示します。関数がパラメーターを渡す方法と同様に、ジェネリックスの T は関数内のパラメーターであり、型変数と見なすことができます。このように、ジェネリックは型を渡す機会を与えてくれます。

汎用関数

ジェネリックは、インターフェイス ジェネリック、クラス ジェネリック、関数ジェネリックに分類されます。上記のクラスジェネリックスとは、クラスを定義する際に関連する値が特定の型を指定するのではなく、使用時にジェネリッククラスを利用して特定の型を渡すことで柔軟に型を定義することを意味します。たとえば、typescript の一般的な Promise クラスは、典型的なジェネリック クラスです。これを使用する場合は、Promise コールバックで値の型を指定するために型を渡す必要があります。ジェネリック関数は、ジェネリック インターフェイスを実装する関数です。次のコードを見てみましょう

function getDataFromUrl<T>(url: sting): Promise<T> {
    
    
  return new Promise((resolve) => {
    
    
    setTimeout(() => {
    
    
      resolve(data); //
    });
  });
}

このコードでは、URL を渡してデータを取得するメソッドをシミュレートします。このメソッドは Promise を返し、Promise の解決値はジェネリック型 T で指定されます。非同期リクエストによって返される応答値のデータ型は静的ではなく、さまざまなインターフェイスに応じて変化するため、上記の記述方法は ajax リクエストでよく見られます。

一般的な制約

泛型函数先ほどのコードでは、Tこのジェネリック型を渡しました。これは次のように使用できます。

getDataFromUrl<number>('/userInfo').then(res => {
    
    
  constole.log(res);
})

この時点では、応答のデータ型を数値に制限していますが、もちろん、文字列、配列、または ts 標準を満たすその他の型として指定することもできます。T の範囲を指定したい場合はどうすればよいでしょうか? 次に、次のような一般的な制約を使用します。

function getDataFromUrl<T extends keyof string|number>(url: sting): Promise<T> {
    
    
  return new Promise((resolve) => {
    
    
    setTimeout(() => {
    
    
      resolve(data); //
    });
  });
}

extends キーワードを使用して T の範囲をstringnumberその範囲内に制限します。呼び出すとき、T の型値はこれら 2 つの型のみにすることができます。そうでない場合はエラーが報告されます。上記の
概念を理解した後、自動化を実現し始めることができます。必要な関数。今すぐ関数をプロンプトします。まずは実現したい機能がどのようなものかを見てみましょう

import api from '@/service';
private async getUserInfo () {
    
    
  const res = await api('getUserInfo', {
    
    uid: 'xxxxx'})
  // 省略若干代码
}

私たちが達成したいのは、上記の API メソッドを入力して呼び出すときに、getUserInfoインターフェイス名を自動的に入力し、同時にパラメーターに制限を設定できることです。そして、私たちのサービスは次のようになります。目標は次のとおりです
。明らかに、下に降りることができます。

最初のステップは、serviceice がメソッド名またはインターフェイス名の入力を自動的に要求できるようにする方法です。

必要なインターフェイス メソッドを含むインターフェイスを定義します。

interface Service {
    
    getUserInfo: (arg:any) => any}
const service: Service = {
    
    getUserInfo: (params) => {
    
    return get('/usrInfo', params)}}

上記のコードはメソッド名の自動プロンプトを実現していますが、それだけでは十分ではなく、メソッド名を自動的にプロンプ​​トするだけでなく、対応するメソッドによって渡されるパラメータの型もプロンプトすることが目標です。次に、最初にさまざまなメソッドのパラメータを定義します。
ここでは、params.d.ts という名前のパラメータ型宣言ファイルを作成します。これは、メソッド params.d .ts に対応するさまざまなインターフェイスまたはパラメータ型を含む Params モジュールを定義するために使用されます

export interface getUserInfoParams {
    
    
  name: string;
  uid: string;
}

export default interface Params {
    
    
    getUserInfo: getUserInfoParams
}

さて、service.d.ts という名前の別のファイルを作成しましょう。これは、対応するインターフェイスを含むサービス クラスを宣言するために使用されます。

import {
    
     AxiosPromise } from "axios";
import Params from './params';
import {
    
     AnyObj } from "COMMON/interface/common";

interface ResponseValue {
    
    
    code: number;
    data: any;
    msg?: string;
}

type ServiceType = (params: AnyObj) => Promise<ResponseValue>;
export type Service = {
    
    
  getUserInfo: ServiceType
}

このように、2 つのファイルと 2 つの大きなモジュール (Service および Params タイプ) があるため、サービス内のメソッドを params 内の対応するメソッドのパラメータ タイプにリンクするにはどうすればよいでしょうか?
まず、サービスのメソッド、つまりキーはparamsのキーと同じでなければなりませんが、このようにサービスインターフェースを定義すればよいのでしょうか?

interface Service extends Params {
    
    }

明らかに、これにより Service インターフェイスが params と同じキーを持つことができますが、これは params のキーを継承するだけでなく、キーのタイプも継承します。ただし、欲しいのは
params のキーだけです。Service のキーの型は戻り値の型が Promise であるメソッドであり、本来の意図とは異なります。
上で typescript の汎用ツール クラスについて説明しましたが、その 1 つは と呼ばれます。レコード、およびこの機能は、タイプ T のキーのタイプを別のタイプに変換することであり、この別のタイプはユーザーによって指定されます。
ここではこの特徴を利用して、対応するキーを取得できるだけでなく、Service のキーの型が戻り値の型が Promise であるメソッドであるという上記の条件を満たすことができます。
以下のように、達成できます

type Service = Record<keyof Params, ServiceType>

ここでは、Params のキーを抽出して Record に渡し、同時にキーの型を ServiceType として指定することで、Service 型が実現され、その属性は Params と同じで、属性の型は ServiceType になります。
変更後はこんな感じ

import {
    
     AxiosPromise } from "axios";
import Params from './params';
import {
    
     AnyObj } from "COMMON/interface/common";

interface ResponseValue {
    
    
    code: number;
    data: any;
    msg?: string;
}

type ServiceType = (params: AnyObj) => Promise<ResponseValue>;
export type Service = Record<keyof Params, ServiceType>


これまでのところ、Service インターフェイスには Params 型と特定の関係があります。つまり、Service に表示されるキーは Params に表示される必要があります。そうでない場合、型の検出は失敗します。これにより、開発中に毎回インターフェイス メソッドが追加されることが保証されます。を使用するには、最初にメソッドのパラメーターの型を Params で定義する必要があります。

2 番目のステップは、サービス メソッドを呼び出すときにインターフェイスのパラメータ タイプを自動的に要求することです。

メソッドを呼び出すときに、メソッドのパラメーターの型をメソッド名 (つまり、サービスのキー) に関連付ける方法を見つける必要があります。
実際、この関連付けを実現する簡単な方法があります。つまり、サービスで属性のメソッドを定義するときに、対応するメソッドのパラメータ型をパラメータに直接定義しますが、これは使用目的を満たしません。ジェネリックの。ジェネリックスを使っているので、ジェネリックスにはパラメータを渡すという特徴があると思います。サービス内でメソッドを呼び出す際に、メソッド名に対応するパラメータの型もParamsから抽出できれば目的は達成できるでしょうか?まず、サービス内のキーをパラメータとして渡すことができる関数を定義し、サービス内のメソッドを呼び出し、メソッドの戻り値を返します。

const api = (method) {
    
    return service[method]()};

このメソッドはサービスを呼び出すときにパラメータを渡すことができる必要があるため、次のようになります。

const api = (method, parmas) {
    
    return service[method](params)};

上記の目的に従って、API 関数のパラメーターの型をジェネリックに設定し、このジェネリック パラメーターを Service クラスのメソッド名に制約する必要があります。上で述べたとおり、制約では extends キーワードを使用できます。
それで、あります

const api<T extends keyof Service> = (method: T, params: Params[T]){
    
    return service[method](parmas)};

このようにして、API を通じてサービスを呼び出し、メソッド名とパラメーターの型のヒントを得ることができます。
これまでのところ、API メソッドとパラメータを自動的にプロンプ​​トする小さな機能が実現されており、開発プロセス中、API メソッドが呼び出されている限り、オプションの API メソッドが自動的にポップアップされます。複雑なプロジェクトでは、開発者は対応するインターフェイスとパラメーターの型を params で定義するだけでよく、呼び出されるたびにプロンプ​​トが表示されるため、インターフェイスのドキュメントを常に確認する手間がなくなり、開発効率が大幅に向上します。もちろん、この小さな機能には、応答データを自動的に要求する機能を追加することもできますが、これについてはここでは触れず、皆さんに考えていただくことにします。
完全なコード:
params.d.ts

export interface getUserInfoParams {
    
    }

export default interface Params {
    
    
    getUserInfo: getUserInfoParams
}

サービス.d.ts:

import {
    
     AxiosPromise } from "axios";
import Params from './params';
import API from './api';
import {
    
     AnyObj } from "COMMON/interface/common";

interface ResponseValue {
    
    
    code: number;
    data: any;
    msg?: string;
}

type ServiceType = (params: AnyObj) => Promise<ResponseValue>;
export type Service = Record<keyof Params, ServiceType>

サービス/index.ts:

import {
    
     get, post } from 'COMMON/xhr-axios';
import {
    
    Service} from './types/service';
import Params from './types/params';

const service:Service = {
    
    
    getUserInfo: (params) => {
    
    
        return get('/usrinfo', params);
    }
}


const api = <FN extends keyof Params>(fn: FN, params: Params[FN]) => {
    
    
    return service[fn](params)
}

// 用法
// import api from '@/service/index'
// api('getUserInfo', {})

export default api;

使用:

private async getUserInfo () {
    
    
  const res = await api('getUserInfo', {
    
    uid: 'xxxxx'})
  // 省略若干代码
}

議論するメッセージを残してくださる皆様を歓迎します。スムーズな仕事と幸せな生活をお祈りしています。

私は bigo のフロントエンドです。次号でお会いしましょう。

おすすめ

転載: blog.csdn.net/yeyeye0525/article/details/121329384