简体   繁体   中英

How to type function return type with optional object of formatter function

I'm trying to figure out how to properly type the return value when an object with optional formatter functions is involved.

For a function with a single value it is working fine.

type Params = {
    id?: number
    created?: string
}

type FormatFn<TParam extends keyof Params, TValue> = (data: Params[TParam]) => TValue

type Result<TValue> = {
    param: TValue
}

declare function query<TParam extends keyof Params, TValue = Params[TParam]>(
    param: TParam,
    formatter?: FormatFn<TParam, TValue>
): Result<TValue>;

// Works as expected 'created' is type 'Date'
const { param: created } = query('created', (created) => new Date(created || ''))

I want to add a version where you can input an array of strings and an object of optional formatter functions.

Here is a Playground with my attempt.

This would be my expected output:

queries(['id', 'created'], {
    created: (created) => new Date(created || '')
})

// Instead of return type
// {
//     params: {
//         id: number,
//         created: string
//     }
// }
// 
// I want to have this, because of the formatter function
// {
//     params: {
//         id: number,
//         created: Date
//     }
// }
type Params = {
    id?: number
    created?: string
}

type Elem = keyof Params;

type Fn = (value: any) => any

type Predicate<T extends Elem> = Record<T, (value: Required<Params>[T]) => any>

type Reducer<
    Arr extends ReadonlyArray<Elem>,
    Result extends Record<string, any> = {}
    > = Arr extends []
    ? Result
    : Arr extends readonly [infer H, ...infer Tail]
    ? Tail extends ReadonlyArray<Elem>
    ? H extends Elem
    ? Reducer<Tail, Result & Predicate<H>>
    : never
    : never
    : never;

/**
 * Pure js/ts analogy
 */

const reducerPredicate = (elem: string) => ({ [elem]: () => null });

const reducer = <Arr extends string[]>(
    arr: Arr,
    result: { [prop: string]: string } = {}
) => {
    if (arr.length === 0) {
        return result; // 1. end of recursion, this is the last call
    }

    const [head, ...tail] = arr; // 2. from first and before the last

    return reducer(tail, { ...result, ...reducerPredicate(head) });
    // no need for never branch
};

type Result<
    P extends ReadonlyArray<keyof Params>,
    Defaults extends Partial<Record<keyof Params, Fn>>,
    Cache extends P[number] & keyof Defaults = P[number] & keyof Defaults> =
    & { [Prop in Exclude<P[number], Cache>]: Required<Params>[Prop] }
    & { [Prop in keyof Defaults]: Defaults[Prop] extends Fn ? ReturnType<Defaults[Prop]> : never }

function queries<TParams extends Array<keyof Params>, TValues extends Partial<Reducer<TParams>>>(
    params: [...TParams],
    formatter?: TValues
): Result<TParams, TValues> {
    return null as any
}

const multiple = queries(['id', 'created'], {
    created: (created /** string */) => 42,
    id: (value /** number */) => 42
})

Playground

Here , in my blog, you can find more explanation.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM