通用打字稿:来自T值键的通用类型

时间:2020-03-03 07:15:39

标签: typescript generics

如何从TSource和给定的属性名称(TSource的键)推断结果类型(TTarget)?

我具有以下功能,可将定义的属性复制到新对象中:

export declare type PropertyNamesOnly<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

CopyProps<TSource, TTarget>(source: TSource, ...props: PropertyNamesOnly<TSource>[]): TTarget {
    const result: any = {};  
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
}

现在我可以这样使用它:

class Props { a: string = "a"; b: string = "b"; c: string = "c"; }
const props = new Props();

const copy = CopyProps<Props, Omit<Props, "b">>(props, "a", "c");

expect(copy.a).to.equal("a");
// copy has omitted property b
expect((copy as any).b).to.be.undefined;
expect(copy.c).to.equal("c");

但是我不想定义TSource和TTarget。我想要这个:

CopyProps<TSource>(source: TSource, ...props: PropertyNamesOnly<TSource>[]): TypeFromProps<props> {
    const result: any = {};  
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
}

// Then copy should contains only property a and c
const copy = CopyProps(props, "a", "c");

如何获取TypeFromProps类型?

解决方案:

static PickProps<
  TSource,
  Props extends PropertyNamesOnly<TSource>,
  TTarget extends Pick<TSource, Props>>
  (source: TSource, ...props: Props[]): TTarget {
    const result: any = {};
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
  }

  static OmitProps<
    TSource,
    Props extends PropertyNamesOnly<TSource>,
    TTarget extends Omit<TSource, Props>>
    (source: TSource, ...props: Props[]): TTarget {
      const result: any = {};
      const keys = Object.keys(source).filter(k => props.some(p => p !== k)) as (keyof TSource)[];

      for (const key of keys) {
          result[key] = source[key];
      }
    }

1 个答案:

答案 0 :(得分:1)

为了实现此目标,我们需要将属性类型提升为泛型。考虑:

declare type PropertyNamesOnly<T> = { [K in keyof T]: T[K] extends Function ? never : K }[keyof T];

function CopyProps<
  TSource, 
  Props extends PropertyNamesOnly<TSource>, 
  TTarget extends Pick<TSource, Props>>
(source: TSource, ...props: Props[]): TTarget {
    const result: any = {};  
    for (const prop of props) {
      result[prop] = source[prop];
    }
    return result;
}

class Props { a: string = "a"; b: string = "b"; c: string = "c"; }
const props = new Props();

const copy = CopyProps(props, "a", "c"); // copy has a and c

// second check with function
const example2 = { a: 'a', b: () => { }, c: 1 };
const copy2 = CopyProps(example2, "a", "b"); // correct error as b is a function

const example3 = { a: 'a', b: () => { }, c: 1 };
const copy3 = CopyProps(example2, "a", "c"); // is ok has a and c

最重要的部分:

  • Props extends PropertyNamesOnly<TSource>-我们说道具是TSource的键,但没有功能值类型的键
  • TTarget extends Pick<TSource, Props>-我们说归还将是TSource的对象,并由Props挑选道具
  • ...props: Props[]我们声明我们的道具是泛型,以便类型推断起作用

Playground link