简体   繁体   中英

typescript interface conditional optional parameters

Can we achieve this :

interface IMyInterface{
    firstname:string  // this means firstname is mandatory

    name:string  // this also means name is mandatory
}

How can I say, either one of firstname or name is optional ( ? ), depending on if the other one is provided ?

Or if that's not possible, what are the other options ?

EDIT : This is not a duplicate of Typescript Interface - Possible to make "one or the other" properties required? .

We don't want to create a separate interface for every single optional element simply because the maintenance and naming and the refactoring will be a pain in the neck and it's not reusable.

Here is a generic way of saying "OneOf" these keys, you might be able to use it here:

type EachOfTmp<T> = {// to make OneOf less gross
  [K in Keys<T>]: {
    _: {[X in K]: T[K]};
  }
};

// require only one of the keys
export type OneOf<T> = EachOfTmp<T>[Keys<T>]["_"] & Partial<T>;

const thing1: OneOf<{ a: number; b: number }> = { a: 2 } // valid
const thing2: OneOf<{ a: number; b: number }> = { b: 2 } // valid
const thing3: OneOf<{ a: number; b: number }> = {} // invalid

EDIT: oops, I forgot I use this convenience Keys deal -

export type Keys<T> = keyof T;
export function Keys<T>(o: T) {
  if (!o) {
    return [];
  }
  return Object.keys(o) as Keys<T>[];
}

Another way:

    interface IName {
        name:string 
    }

    interface IFirstName {
        firstname:string 
    }

    let x: IName | IFirstName;
    x = {}; // Error
    x = { name: "" }; // Ok
    x = { firstname: "" }; // Ok
    x = { name: "", firstname: "" }; // Ok

If IMyInterface has other members that need to be preserved, I'd like to propose a more generalized version of the @Catalyst response.

type EachExpanded<T> = {
    [key in keyof T]: { [subKey in key]: T[key]; }
};

type FixedSubset<T, U> = Pick<T, Exclude<keyof T, U>>;

type AtLeastSubset<T, U> = Pick<T, Extract<keyof T, U>>;

type AtLeaseOne<T, U> = FixedSubset<T, U> & EachExpanded<AtLeastSubset<T, U>>[keyof AtLeastSubset<T, U>];

const example1: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { a: 3, b: 4, c: '4' } // valid
const example2: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { a: 1, c: '1' } // valid
const example3: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { b: 2, c: '2' } // valid
const example4: AtLeaseOne<{ a: number; b: number; c: string; }, 'a' | 'b'> =
    { c: '3' } // invalid

Please keep in mind that this response uses the Exclude and Extract keyword introduced in TypeScript version 2.8, which was just released. These are part of the conditional types.

In the code, we assume type T is the original type or interface and U is the set of keys, of which at least one must be present.

The way it works is that it creates a type by excluding properties that need to be implemented based on the original type T . This is defined using Pick<T, Exclude<keyof T, U>> which contains everything not in U .

Then, another type is created only containing the elements of which at least one must be present, Pick<T, Extract<keyof T, U>> .

EachExpanded stores the type for each one the special sets under the same key. For example, if keys 'a' and 'b' are to become conditionally optional for the example above, EachExpanded creates the following type:

{
    a: { a: number; };
    b: { b: number; };
}

This will be used in the final type with an intersection operator, so at least one of them is enforced to be present.

Essentially, for the example above, we will end up with the following:

{ c: string; } & ({ a: number; } | { b: number; })

You can write it like this.

interface ContactName{
    firstname?: string;
    name: string;
}

interface ContactFirstName{
    firstname: string
    name?: string
}

type Contact = ContactName | ContactFirstName;

And when you use Contact interface, either name or first name become mandatory.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM