Ako spraviť, aby funkcia akceptovala pole reťazených modifikátorov s správnym typom v TypeScripte


Chcem upraviť dáta pomocou reťazca funkčných modifikátorov, ale so správnym typovaním v TypeScripte. Je to možné?

const addA = (data: {}) => {
  return {
    ...data,
    a: "test"
  }
}

const addB = (data: {}) => {
  return {
    ...data,
    b: "test"
  }
}

const updateA = (data: {a:string}) => {
  return {
    ...data,
    a: data.a + " test"
  }
}

const func = <T extends ((data: Record<string, any>) => Record<string, any>)[]>(modifiers: T): unknown => {
  return modifiers.reduce((acc, modifier) => {
    return modifier(acc)
  }, {})
}

console.log(func([addA])) // Pass, Should pass.
console.log(func([addA, addB])) // Pass, Should pass.
console.log(func([addA, addB, updateA])) // Error, Should pass.
console.log(func([updateA])) // Error, Should error. Unexpected runtime undefined value.

Detské ihrisko

Odpoveď č. 1

Upozornenie: Vaše funkcie vždy rozširujú vstup vo výstupe, ale vaše typy to vlastne neodzrkadľujú. Mohli by ste použiť generiká na jeho zobrazenie, ale to by riešenie nižšie zložitejšie. Pokiaľ sa dodrží konvencia, že to, čo vystupuje z funkcie, je aspoň to, čo do nej vstúpilo, riešenie nižšie funguje.

Môžete použiť typový parameter na odvodenie poľa funkcií ako n-tice. Potom môžete použiť rekurzívny podmienený typ na vytvorenie n-tice toho, čo očakávate, že typy funkcií budú v skutočnosti. Následne pretínate typový parameter s týmto validačným typom. Ak je všetko v poriadku, kontrola je v podstate bez efektu, inak dostanete chybu z pretínania:

type FunctionArray = Array<(p: any) => any>;
type ValidateChain<T extends FunctionArray , Input = {}, Result extends any[] = []> = 
  T extends [(data: Input) => infer R, ...infer Tail extends FunctionArray] ? ValidateChain<Tail, Input & R, [...Result, T[0]]>:
  T extends [(...p: infer P) => infer R, ...infer Tail extends FunctionArray] ? ValidateChain<Tail, Input & R, [...Result, (data: Input) => R]>:
  Result

type MergeAll<T extends FunctionArray , Input = {}> = 
  T extends [(data: any) => infer R, ...infer Tail extends FunctionArray] ? MergeAll<Tail, R & Input>: Input

const func = <T extends [] | FunctionArray>(modifiers: T & ValidateChain<T>): MergeAll<T> => {
  return (modifiers as T).reduce((acc, modifier) => {
    return modifier(acc)
  }, {}) as MergeAll<T> 
}

let r1 = func([addA]) // Pass
let r2 = func([addA, addB]) // Pass
let r3 = func([addA, addB, updateA]) //Pass.
let r4 = func([addA, addB, updateC]) // Fails
let r5 = func([updateA]) // Fails

Odkaz na ihrisko

O podmienkových typoch sa môžete dozvedieť viac v príručke

Súvisiace články