【Typescript技巧】如何從「參數類型」推導出合適的「返回類型」

問題描述

爲下面描述的map函數添加一個更好的Typescript類型標註,提升使用者的類型安全性(若是不按下面的方式使用,就會報錯):html

map是一個用來作數組mapping的高階函數,先接收mapping function(將【輸入數組的項】映射爲【輸出數組的項】),而後接收輸入數組,返回輸出數組。
當mapping function沒有傳入時,mapping function默認爲一個不作任何轉化的函數(即x=>x)。typescript

用代碼來表示,就是:數組

const map = (fn = x => x) => arr => {
  const result = [];
  for (const item of arr) {
    result.push(fn(item));
  }
  return result;
};

Typescript Playground安全

方案1:函數重載

function map(): <V>(arr: V[]) => V[];
function map<V, R>(fn: (value: V) => R): (arr: V[]) => R[];
function map(fn = (x: any) => x) {
  return (arr: any[]) => {
    const result: any[] = [];
    for (const item of arr) {
      result.push(fn(item));
    }
    return result;
  };
}

// 測試

const emptyMap = map();
const result1 = emptyMap([1, 2, 3]);
const result2 = emptyMap(["1", "2"]);

const mapNumberToString = map((v: number) => "str");
const result3 = mapNumberToString([1, 2, 3]);
// const result4 = mapNumberToString(["1", "2"]); // 報錯,類型檢查有效
// const mapNumberToString2 = map((v: number) => "str", [1, 2]); // 報錯,類型檢查有效

方案2:類型推導

事實上類型推導也可以作到(雖然可維護性降低不少),就當作是對Typescript類型推導的一個練習吧!app

真實項目中遇到這種狀況,應該選擇可維護性更高的方式。
type FnBasic = (value: any) => any;

type InferV<Fn> = Fn extends ((value: infer V) => any) ? V : never;
type InferR<Fn> = Fn extends ((value: any) => infer R) ? R : never;

const map = <Args extends [FnBasic?]>(...args: Args) => {
  // 經過tuple types來拿到可選參數的實際類型
  // https://stackoverflow.com/a/51489032
  type FnType = Args[0];
  const fn: FnBasic = args[0] || (x => x);
  type ReturnType = FnType extends (undefined)
    ? <V>(arr: V[]) => V[] // 返回一個帶泛型的函數,從而V可以在被調用時肯定
    : (arr: InferV<FnType>[]) => InferR<FnType>[]; // V已經可以推導出來

  const ret: ReturnType = ((arr: any[]) => {
    const result = [];

    for (const item of arr) {
      result.push(fn(item));
    }

    return result;
  }) as any;

  return ret;
};

// 測試

const emptyMap = map();
const result1 = emptyMap([1, 2, 3]);
const result2 = emptyMap(["1", "2"]);

const mapNumberToString = map((v: number) => "str");
const result3 = mapNumberToString([1, 2, 3]);
// const result4 = mapNumberToString(["1", "2"]); // 報錯,類型檢查有效
// const mapNumberToString2 = map((v: number) => "str", [1, 2]); // 報錯,類型檢查有效

其中主要的知識點是:函數

  1. 利用tuple types來拿到可選參數的實際類型(若是不用這個方式,可選參數的類型很難用來作類型推導)
  2. 基於參數類型推導出想要的返回值類型(涉及到infer的使用)
相關文章
相關標籤/搜索