【问题标题】:TypeScript function to assign specific key to object and letting TypeScript know about itTypeScript 函数将特定键分配给对象并让 TypeScript 知道它
【发布时间】:2023-01-12 05:12:28
【问题描述】:

我有一个名为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 不知道我们正确地为 [key] 分配了值,因为在 liftedFunction 的返回类型中显式输入。

你怎么能避免 TypeScript 在这里因为一切正常而对你大喊大叫呢?

我尝试省略 liftedFunction 的返回类型的显式类型。但是,TypeScript 不知道函数结果分配给的键的正确返回类型。

【问题讨论】:

  • 您的问题可以简化为以下内容:tsplay.dev/N5OJ2W - 具有与之关联的通用类型的键的使用得到扩大。

标签: typescript object key typescript-typings typescript-generics


【解决方案1】:

这是一个生成正确类型结果的 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 &amp; { [P in TOutProp]: TOut }

LiftObject 类型的其他好处:

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

【讨论】:

    猜你喜欢
    • 2021-01-04
    • 2012-12-26
    • 2020-06-16
    • 2019-09-04
    • 2020-04-17
    • 1970-01-01
    • 2018-04-09
    • 2016-01-14
    • 2023-01-16
    相关资源
    最近更新 更多