typescript 类型脚本:泛型:是否有一种安全而严格的方法来挑选属性?(必须存在于源AND目标类型上)

lskq00tm  于 2022-12-05  发布在  TypeScript
关注(0)|答案(1)|浏览(113)

我想将类型A转换为类型B,但如果我选择的属性未在两种类型上定义,我想得到警告。

type Source = {
  id: number;
  foo: string;
  onlyOnSource: string;
}

type Target = {
  id: number;
  foo: string;
  onlyOnTarget?: string;
}

const source: Source = {
  id: 1,
  foo: 'hello',
  onlyOnSource: 'test',
}

const transformer1 = (response?: Source): Partial<Target> => {
  return {
    id: response?.id,
    foo: response?.foo,
    // wrong: undefined, // cool. error is catched: Object literal may only specify known properties, and 'wrong' does not exist in type 'Partial<Target>'
    // onlyOnTarget: response?.onlyOnTarget, // cool. error is catched: Property 'onlyOnTarget' does not exist on type 'Source'
  };
};

...总的来说,这是最好的。但是我必须写所有的属性两次(id,foo)。而且可能会给它们赋值错误(例如id: response?.foo
所以我尝试了一个通用的“safePick”函数(它类似于lodash/pick,但是当属性不存在时会发出警告)

const safePick = <T, K extends keyof T>(source?: T, ...keys: K[]): Partial<Pick<T, K>> => {
      if (!source) return {};
      const target: Partial<Pick<T, K>> = {};
      keys.forEach(key => {
        if (source[key] !== undefined) target[key] = source[key];
      });
      return target;
    };

const transformer2 = (response?: Source): Partial<Target> => {
  // return safePick(response, 'id', 'foo', 'wrong'); // cool. error is catched: Argument of type '"wrong"' is not assignable to parameter of type 'keyof Source'
  return safePick(response, 'id', 'foo', 'onlyOnSource'); // WRONG! 'onlyOnSource' should not be alowed on type Target!
};

但是,当我将属性“onlyOnSource”赋给类型Target时,这并不是抱怨。
即使我去掉了“部分”部分,它还是不在乎。
但是我希望这个操作失败并给出一个警告,理想的情况是以一种通用的方式,这样我就不必手动传递两种类型的所有键。
我试了很多方法,甚至用了“zod”,但都找不到一个好的解决方法,有人有什么好主意吗?

jogvjijk

jogvjijk1#

你必须把Target类型也作为函数的参数,这是因为带有额外字段的对象将是Patrial<Target>返回类型的子类型,所以它不会像你希望的那样失败。
然后使用该类型计算一些错误的返回类型,如果有额外的字段。

const safePick = <Target>() => {
    return <T, K extends keyof T>(source?: T, ...keys: K[]): TargetType<Target, K, T> => {
        // @ts-ignore
      if (!source) return {};
      const target: Partial<Pick<T, K>> = {};
      keys.forEach(key => {
        if (source[key] !== undefined) target[key] = source[key];
      });
        // @ts-ignore
      return target;
    };
}

计算错误类型的类型级函数如下:

type TargetType<Target, K extends keyof T, T> = Exclude<K, keyof Target> extends never ? Partial<Pick<T, K>> : void

在这里,你检查如果你取Source的所有键并从中排除Target的所有键,结果是什么。如果有never,这意味着Source中没有额外的字段,你可以安全地返回你想要的类型。否则,返回“fail”类型void,这将导致编译错误。

const transformer2 = (response?: Source): Partial<Target> => {
  return safePick<Target>()(response, 'id', 'onlyOnSource'); // WRONG! 'onlyOnSource' should not be alowed on type Target!
};

这将导致:

Type 'void' is not assignable to type 'Partial<Target>'.ts(2322)

相关问题