用于为对象分配特定键并让TypeScript知道它的TypeScript函数

hivapdat  于 2023-01-14  发布在  TypeScript
关注(0)|答案(1)|浏览(123)

我有一个叫做liftSync的函数。

/**
 * Lifts a function into the object context.
 *
 * @param functionToLift - The function to lift into the object context.
 * @param assignKey - The key to assign the result of the function to. Can
 * overwrite an existing key of the object.
 * @param argumentKeys - The keys to use as arguments for the function.
 * @returns A function that takes an object and applies the values of the object
 * for the argument keys to the function and assigns the result to the assign
 * key, then returns that object with the new key.
 *
 * @example
 *
 * A JavaScript version of the function can be easier to understand:
 *
 * ```js
 * const lift = (fn, key, ...args) => (obj = {}) => ({
 *   ...obj,
 *   [key]: fn(...args.map(a => obj[a])),
 * });
 * ```
 *
 * @example
 *
 * ```ts
 * const add = (a: number, b: number) => a + b;
 *
 * const liftedAdd = lift(add, 'sum', 'a', 'b');
 * liftedAdd({ value: true, a: 21, b: 21 });
 * // { value: true, a: 21, b: 21, sum: 42 }
 * ```
 */
export function liftSync<
  FunctionToLift extends (...functionArguments: any[]) => any,
  AssignKey extends string,
  ArgumentKey extends string,
>(
  functionToLift: FunctionToLift,
  assignKey: AssignKey,
  ...argumentKeys: readonly ArgumentKey[]
) {
  return function liftedFunction<
    IncomingObject extends Record<ArgumentKey, any>,
  >(
    object: IncomingObject,
  ): IncomingObject & Record<AssignKey, ReturnType<FunctionToLift>> {
    // @ts-expect-error TS is dumb lol
    return {
      ...object,
      [assignKey]: functionToLift(
        ...argumentKeys.map(argument => object[argument]),
      ),
    };
  };
}

下面是一个演示如何使用它的测试:

import { describe, expect, test } from 'vitest';

import { liftSync } from './lift';

const add = (a: number, b: number) => a + b;

describe('liftSync()', () => {
  test('given a synchronous function, a key and the names for arguments: should lift it into the object context', () => {
    const liftedAdd = liftSync(add, 'sum', 'a', 'b');
    const result = liftedAdd({ value: true, a: 21, b: 21 });

    expect(result).toEqual({
      value: true,
      a: 21,
      b: 21,
      sum: 42,
    });
  });
});

如您所见,我不得不使用@ts-expect-error,因为TypeScript不知道我们在liftedFunction的返回类型中将[key]的值正确地赋值为显式类型。
在这里,如何避免TypeScript因为一切正常而对您大喊大叫?
我试着忽略liftedFunction返回类型的显式类型,但是TypeScript不知道函数结果被赋值的键的正确返回类型。

iszxjhcz

iszxjhcz1#

下面是一个liftSync函数,它生成正确的类型作为结果

type LiftFunction<TParams extends [...any[]], Tout> = (...args: TParams) => Tout;
    
    type LiftObject<TParams extends [...any[]], TProps extends [...string[]]> =
        TParams extends [] ? (TProps extends [] ? {} : never) :
        TParams extends [infer TParam, ...infer TailParam] ? (TProps extends [infer TProp extends string, ...infer TailProps extends string[]] ? { [P in TProp]: TParam } & LiftObject<TailParam, TailProps> : never):
        never;

    export function liftSync<
        TParams extends [...any[]],
        TOut,
        TProps extends [...string[]],
        TOutProp extends string
    > (
        functionToLift: LiftFunction<TParams, TOut>,
        assignKey: TOutProp,
        ...argumentKeys: TProps
    ) {
        return <Tobj extends LiftObject<TParams, TProps>>(obj: Tobj) => ({
            ...obj,
            [assignKey]: functionToLift(...(argumentKeys.map(argument => obj[argument]) as TParams)),
        }) as Tobj & { [P in TOutProp]: TOut };
    }

诀窍是as Tobj & { [P in TOutProp]: TOut }
LiftObject类型带来的其他好处:

  • 它会检查你要举起的物体的类型
  • 它检查您传递的 prop 名称是否与lift函数的参数一样多

相关问题