【问题标题】:Wrap generic TypeScript function to pass through nulls包装通用 TypeScript 函数以传递空值
【发布时间】:2021-10-22 08:41:15
【问题描述】:

Expression<T> 是一个可以评估为T 的类:

class Expression<T> {
  evaluate(): T {
    // ...
  }
}

Expression 有一个解析表达式的静态 parse 方法。它的第二个参数表示期望的类型,并确定返回的Expression的泛型类型:

export interface RGBA {
  r: number;
  g: number;
  b: number;
  a: number;
}

interface TypeMap {
  string: string;
  number: number;
  color: RGBA;
  boolean: boolean;
  [other: string]: any;
}

export class Expression<T> {
  static parse<T extends expression.StylePropertyType>(
    expr: number | string | Readonly<StyleFunction> | Readonly<MapboxExpression>,
    expectedType?: T,
  ): Expression<TypeMap[T]> {
    // ...
  }
}

(完整要点here,上下文为Mapbox style expressions)。

这使您可以解析表达式并以类型安全的方式评估它们:

const colorExp = Expression.parse("red", "color");  // type is Expression<RGBA>
const red = colorExp.evaluate();  // type is RGBA

const numExp = Expression.parse(["+", 1, 2], "number");  // type is Expression<number>
const num = numExp.evaluate();  // type is number

到目前为止一切顺利。现在我想编写一个包装Expression.parse 的函数来传递undefined 值。没有类型的实现很简单:

const parseOrUndef = (expr, type) => expr === undefined ? undefined : Expression.parse(expr, type);

但是如果我想要这些类型呢?理想情况下,我会:

const colorExp = parseOrUndef("red", "color");  // type is Expression<RGBA> | undefined
const red = colorExp?.evaluate();  // type is RGBA | undefined

const numExp = parseOrUndef(["+", 1, 2], "number");  // type is Expression<number> | undefined
const num = numExp.evaluate();  // type is number | undefined

是否可以在 TypeScript 中执行此操作而无需复制/粘贴 Expression.parse 中的所有类型?这是一次尝试:

type AddUndefToTuple<T extends any[]> = T extends [infer First, ...infer Rest]
  ? [First | undefined, ...Rest]
  : [];

const withUndef = <Fn extends (...args: any[]) => any>(fn: Fn) => (
  ...args: AddUndefToTuple<Parameters<Fn>>
): ReturnType<Fn> | undefined => ((args as any)[0] === undefined ? undefined : fn(...args));

const parseOrUndef = withUndef(Expression.parse);

不幸的是,这会导致Expression&lt;any&gt;,因为当您将Fn 拆分为Parameters&lt;Fn&gt; 和Return&lt;Fn&gt; 时,type 参数和返回类型之间的关系会丢失:

const colorExp = parseOrUndef("red", "color");  // type is Expression<any> | undefined
const red = colorExp?.evaluate();  // type is any :(

const numExp = parseOrUndef(["+", 1, 2], "number");  // type is Expression<any> | undefined
const num = numExp?.evaluate();  // type is any :(

有没有办法在不失去这种关系的情况下通用地做到这一点?还是我必须从Expression.parse 复制/粘贴类型?

Full Playground

【问题讨论】:

    标签: typescript


    【解决方案1】:

    问题出在(...args: AddUndefToTuple&lt;Parameters&lt;Fn&gt;&gt;)这一行。

    为了推断出参数的确切字面类型而不是仅仅放置类型约束,您需要借助泛型再次推断它们。

    parseOrUndef 返回没有泛型的函数。没有通用 - 没有推理。

    因此,为了推断 "red" 和 "color" 参数,您需要相应地为它们添加额外的通用 Expr 和 Type。

    考虑这个例子:

    type StylePropertyType =
      | 'color'
      | 'string'
      | 'number'
      | 'enum'
      | 'boolean'
      | 'formatted'
      | 'image';
    
    type MapboxExpression = [string, ...any[]];
    
    interface RGBA {
      r: number;
      g: number;
      b: number;
      a: number;
    }
    
    interface TypeMap {
      string: string;
      number: number;
      color: RGBA;
      boolean: boolean;
      [other: string]: unknown;
    }
    
    class Expression<T> {
      static parse<T extends StylePropertyType>(
        expr: number | string | Readonly<MapboxExpression> | undefined,
        expectedType?: T,
      ): Expression<TypeMap[T]> {
        return null as any;
      }
    
      evaluate(): T {
        return null as any;
      }
    }
    
    // But with withUndef wrapper does not:
    type AddUndefToTuple<T extends any[]> = T extends [infer First, ...infer Rest]
      ? [First | undefined, ...Rest]
      : [];
    
    const withUndef = <Fn extends (...args: any[]) => any>(fn: Fn) =>
      <
        Expr extends Parameters<Fn>[0],
        Type extends Parameters<Fn>[1],
        >(
          ...args: [Expr, Type]
        ): Expression<TypeMap[Type]> | undefined =>
        ((args as any)[0] === undefined ? undefined : fn(...args));
    
    const parseOrUndef = withUndef(Expression.parse);
    
    const colorExp = parseOrUndef("red", "color");  // Expression<RGBA> | undefined
    const red = colorExp?.evaluate();  // RGBA | undefined
    
    const numExp = parseOrUndef(["+", 1, 2], "number");  // Expression<RGBA> | undefined
    const num = numExp?.evaluate();  // number | undefined
    

    Playground

    Here,在我的博客中,你可以找到更多函数参数推断的例子

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2018-08-05
      • 1970-01-01
      • 2015-09-28
      • 1970-01-01
      • 2016-06-07
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多