簡體   English   中英

我可以從輸入參數推斷類型嗎?

[英]Can I infer types from input arguments?

可以從選項對象推斷類型嗎?

這是我人為的例子。

這里的想法是我有一個接受對象的函數,其中字段是轉換函數(將 A 轉換為 B 等)。 這個函數的返回是一個功能相似的對象,只是返回類型略有不同。

我已經放入了一些泛型,但我不確定如何使其工作。 我在 Redux Toolkit 的createSlice()函數中看到了類似的設計,但它非常復雜。

type InputActionFn = (arg: any) => any;
interface Options {
    actions: {
        [K: string]: InputActionFn;
    };
}

interface ActionPayload<B> {
    payload: B;
}
type OutputActionFn<A, B> = (arg: A) => ActionPayload<B>;

interface ConverterReturn {
    actions: {
        [K: string]: OutputActionFn<any, any>;
    };
}

export function createConvertor(options: Options): ConverterReturn {
    const actions: Record<string, OutputActionFn<any, any>> = {};

    // For each key I create a function that returns my converted payload.
    Object.keys(options.actions).forEach(key => {
        actions[key] = arg => {
            return {
                payload: options.actions[key](arg),
            };
        };
    });

    return {
        actions,
    };
}

這就是我計划使用它的方式:

// I can specify as many converter functions here as I want
const conv = createConvertor({
    actions: {
        actionA: (a: number) => a.toFixed(),
        actionB: (b: string) => parseInt(b, 10),
    },
});

// actionA should be typed properly like this:
//   actionA(arg: number) => ActionPayload<string>
const resultA = conv.actions.actionA(5);
console.log(resultA.payload);
// Outputs: "5"

const resultB = conv.actions.actionB('10');
console.log(resultB.payload);
// Outputs: 10

可以使用打字稿來推斷類型

就是這樣:

type InputActionFn = (arg: any) => any;

interface Options {
  [K: string]: InputActionFn;
}

interface ActionPayload<B> {
  payload: B;
}

type OutputActionFn<A, B> = (arg: A) => ActionPayload<B>;

type Actions<T extends Options> = {
  [key in keyof T]: OutputActionFn<Parameters<T[key]>[0], ReturnType<T[key]>>;
};

export function createConvertor<T extends Options = any>(options: T) {
  const actions = {} as Actions<T>;

  // For each key I create a function that returns my converted payload.
  Object.keys(options).forEach((key) => {
    actions[key] = (arg: Parameters<T[keyof T]>[0]) => {
      return {
        payload: options[key](arg)
      };
    };
  });

  return {
    actions
  };
}

const conv = createConvertor({
  actionA: (a: number) => a.toFixed(),
  actionB: (b: string) => parseInt(b, 10)
});

// Action A is hinted as (arg: number) => ActionPayload<string>
const resultA = conv.actions.actionA(5);
console.log(resultA.payload);
// Outputs: "5"

// Action B is hinted as (arg: string) => ActionPayload<number>
const resultB = conv.actions.actionB('10');
console.log(resultB.payload);
// Outputs: 10

導入部分在這里

type Actions<T extends Options> = {
  [key in keyof T]: OutputActionFn<Parameters<T[key]>[0], ReturnType<T[key]>>;
};

我們告訴 TypeScript 將 Actions 類型為一個對象,它的鍵在泛型 T 的 keyof 中。對於每個鍵,我們指定屬性類型是 OutputActionFn 並將相應的類型傳遞給泛型。 Parameters<T>實用程序類型幫助我們推斷您傳遞的輸入函數的參數類型(請參閱https://www.typescriptlang.org/docs/handbook/utility-types.html#parameterstype )。 由於只有一個預期參數,我們選擇Parameters<T>[0]作為我們的參數類型。 然后我們使用另一個實用程序類型ReturnType<T> (參見https://www.typescriptlang.org/docs/handbook/utility-types.html#returntypetype ),它推斷我們傳遞的輸入函數的返回類型。

唯一的問題是 ESLint 會抱怨這個。 可能不得不忽略這一點。

// TS2536: Type 'string' cannot be used to index type 'Actions '.
actions[key] = (arg: Parameters<T[keyof T]>[0]) => {
      return {
        payload: options[key](arg)
      };
    };

編輯:我差點忘了。 這當然也是一個非常重要的細節,不容忽視。

export function createConvertor<T extends Options = any>(options: T) {

傳遞給 createConvertor 函數的泛型 Type 應用於函數的 options 參數。 我們使用 Options 接口,它不會返回預期的類型。 盡管 options 仍然被輸入為類型 Options 的參數,因為我們從 Options 擴展了泛型類型 T。

使用幾個 TypeScript 通用實用程序( inParametersReturnType等),您可以非常靈活地轉換對象類型。

這是您的代碼示例的工作示例:

type Payloader<T extends Record<string, any>> = {
  [Key in keyof T]: (...params: Parameters<T[Key]>) => ({
    payload: ReturnType<T[Key]>
  })
}

function createConvertor<Actions extends Record<string, any>>(options: { actions: Actions }) {
  const actions: Payloader<Actions> = {} as any;

  Object.keys(options.actions).forEach((key) => 
      // @ts-ignore
      actions[key] = ((...args) => ({
        payload: options.actions[key](...args),
      })
  ))

  return { actions }
}

const conv = createConvertor({
  actions: {
    actionA: (a: number) => a.toFixed(),
    actionB: (b: string) => parseInt(b, 10)
  },
})

const resultA = conv.actions.actionA(5);
console.log(resultA.payload);
// Outputs: "5"

const resultB = conv.actions.actionB('10');
console.log(resultB.payload);
// Outputs: 10

* 注意:函數實現使用@ts-ignore ,假設我們不需要正確鍵入實現。

打字稿游樂場

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM