Exclude specific keys or values from the original type

When you want to exclude certain attributes (Omit) or select (Pick), of course, you can Omit the < IOrigin, “key1” | “key2” >, but if have a way to batch processing, the following will show you SAO operation

interface IOrigin {
  [name: number] :string
  name: string
  gender: string
  age: number
  getName: () = > string
  getGener: () = > string
  getAge: () = > number
}

* {* [name: number]: string; * gender: string; * age: number; * getGener: () => string; * getAge: () => number; *} * /
type OmitSimply = Omit<IOrigin, "name" | "getName">

// Infer excludes attributes of a specific Key
type OmitInferKey<T, R> = {
  [K in keyof T as T extends R ? never : K]: T[K]
}

/** * exclude Key type number attributes * {* name: string; * gender: string; * age: number; * getName: () => string; * getGener: () => string; * getAge: () => number; *} * /
type OmitInferKeyNumber = OmitInferKey<IOrigin, number>

/** * exclude Key 'get${string}' attributes * {* [name: number]: string; * name: string; * gender: string; * age: number; *} * /
type OmitInferKeyRegExp = OmitInferKey<IOrigin, `getThe ${string}`>

// Infer excludes specific Value attributes
type OmitInferValue<T, R> = {
  [K in keyof T as T[K] extends R ? never : K]: T[K]
}

* {* [name: number]: string; * name: string; * gender: string; * age: number; *} * /
type OmitInferKeyFunction = OmitInferValue<IOrigin, Function>
Copy the code

Here’s how to Omit anything and you can use one to “Pick” anything.

Some attributes of a type can only be “one or the other”

When we not only define the type of the parameter, but also if the parameter is object, it is possible that two properties of object are in conflict and can only be “one or the other”.

interface IMyParams {
  a: number;
  b: number;
}

// If we need to define a parameter as an object and its attribute A or b must be passed one
function calc(params: IMyParams) :number{
  if (params.a) {
    console.log(params.a + 1);
  } else {
    console.log(params.b + 1); }}Copy the code

The following defines an EitherOr type to handle this situation:

type FilterOptional<T> = Pick<
  T,
  Exclude<
    {
      [K in keyof T]: T extends Record<K, T[K]> ? K : never;
    }[keyof T],
    undefined
  >
>;

type FilterNotOptional<T> = Pick<
  T,
  Exclude<
    {
      [K in keyof T]: T extends Record<K, T[K]> ? never : K;
    }[keyof T],
    undefined
  >
>;

type PartialEither<T, K extends keyof any> = { [P inExclude<keyof FilterOptional<T>, K>]-? : T[P] } & { [PinExclude<keyof FilterNotOptional<T>, K>]? : T[P] } & { [PinExtract<keyof T, K>]? :undefined };

type Object = {
  [name: string] :any;
};

export type EitherOr<O extends Object, L extends string, R extends string> = 
  (
    PartialEither<Pick<O, L | R>, L> | 
    PartialEither<Pick<O, L | R>, R>
  )  & Omit<O, L | R>;
Copy the code

Use examples:

// Either a or B must be passed
type RequireOne = EitherOr<
  {
    a: number;
    b: string;
  },
  'a'.'b'
>;

// Choose a or B, or neither
typeRequireOneOrEmpty = EitherOr< { a? :number; b? :string;
  },
  'a'.'b'
>;
Copy the code

Practical application:

interface IColumn {
  title: string;
  dataIndex: string;
  render: () = > React.ReactNode;
}
// Those familiar with ANTD should know that the other dataIndex is meaningless if render is passed
// To put it another way, they are "either" attributes
interface ITableProps {
  columns: Array<
    EitherOr<
      IColumn,
      'dataIndex'.'render'
    >
  >;
}
function Table(props: ITableProps){
  // TODO
}
Copy the code