preface

There is an object that has two attributes: name and title. Only one of these two attributes can appear when assigned. For example, title cannot appear when name occurs, and name cannot appear when title occurs.

How would you define this type in TypeScript? This article takes you through the implementation of a mutex type to solve this problem. Interested developers are welcome to read this article.

Front knowledge

Before we can implement it, we need to understand a few basics.

The definition of multiple attributes of the same type in an object

There is an object that contains five optional attributes a, B, C, D, and E, all of which are of type String. Most people would define them as follows:

typeobj = { a? :string; b? :string; c? :string; d? :string; e? :string;
}
Copy the code

So, is there a better way 😼, the answer is there, please look at my performance:

type obj = { [P in "a" | "b" | "c" | "d" | "e"]? :string };
Copy the code

Never type

In TypeScript it has a special type, never, which is a subtype of all types and cannot be subdivided, meaning that no type can be assigned to it except itself.

Let’s take an example to illustrate the above statement, as follows:

  • We defined a variableamazingGives it type never.
  • We gave it different types of values, and it all failed because it couldn’t be subdivided anymore.
let amazing: never;
amazing = 12;// Error: Amazing is never assigned to number
amazing = true;// Error: Amazing is never cannot be assigned to Boolean
amazing = "Amazing.";// Error: Amazing is never and cannot be assigned to string
amazing = {};// Error: amazing is never and cannot be assigned to {}
amazing = [];// Error: Amazing cannot be assigned to type []
Copy the code

Discard attributes in union types

Have a set of combined type “a” | “b” | | “c” “d”, b and c, we want to eliminate attributes in TS provides a function called Exclude, it can be used to do it, takes two parameters:

  • UnionType indicates the UnionType
  • ExcludedMembers Attributes that need to be culled

The usage method is as follows:

type P = Exclude<"a" | "b" | "c" | "d"."b" | "c"> // "a" | "d"
Copy the code

Converts all attributes in an object to a federated type

Have an object which contains two optional attribute name, title, and we want to put it into joint type name | the title, in TS provides a function called keyof, he can be used to deal with this problem, using method is as follows:

type A =  { [P in "name" | "title"]? :string };

type UnionType = keyof A; // "name" | "title"
Copy the code

Implement mutually exclusive types

With this knowledge in hand, we can then use it to define a mutex type to solve the problem described at the beginning of this article.

Next, let’s sort out the implementation idea:

  • Implement an exclusion type that excludes attributes from object type A and sets the excluded attribute type to never, resulting in A new object type.
  • Implement A mutually exclusive type based on the exclusion type, substitute A and B object types into the exclusion type, exclude each other, and join the two results with the or operator.

Smart developers have probably figured out how, yes, some of the properties are set to Never. 🤓

The implementation code

Next, let’s look at the code implementation, as follows:

// Define the exclusion type: remove U from T, keyof fetches all keys of T and U, limits P to all keys of T, and sets its type to never
type Without<T, U> = { [P inExclude<keyof T, keyof U>]? :never };

// Define a mutex in which only one T or U can occur (for mutual exclusion, the culled side must exist)
type XOR<T, U> = (Without<T, U> & U) | (Without<U, T> & T);
Copy the code

Note: We use generics for type reusability. Developers unfamiliar with this should look elsewhere: TypeScript Chinese — generics

The test case

Let’s substitute the problem at the beginning of this article into the implementation code above to see if it can solve it 😌, as shown below:

/ / A type
type A = {
  name: string;
};

/ / B type
type B = {
  title: string;
};

// Only one of the two types can occur
type AOrB = XOR<A, B>;

// Test the transfer of values
const AOrB1: AOrB = { name: "Name" }; // The compiler passes
const AOrB2: AOrB = { title: "Title" }; // The compiler passes
const AOrB3: AOrB = { title: "Title".name: "Name" }; Type '{title: string; name: string; }' is not assignable to type 'AOrB'.
const AOrB4: AOrB = { name: "Name".otherKey: "" }; // error: Type '{name: string; otherKey: string; }' is not assignable to type 'AOrB'.
Copy the code

When two attributes appear together, the editor directly throws a type error (we set all excluded attributes to never, so it will return a type error when you assign any value to them), as shown below:

Use cases and dismantling

Some developers may be confused by the above test cases. If they are broken down, they will not be understood 😹. I will break them down as follows:

typeAOB = ({ name? :never} and {title: string; }) | ({ title? :never} and {name: string;
    });

// Test the transfer of values
const a: AOB = { name: "Name" }; // The compiler passes
const b: AOB = { title: "Title" }; // The compiler passes
const c: AOB = { title: "Title".name: "Name" }; / / an error
const d: AOB = { title: "Title".otherKey: "" }; / / an error

Copy the code

If there are still some developers who don’t understand this, please click on it in the editor. If you don’t understand this, please bookmark this article and learn it later when you have time.

Write in the last

At this point, the article is shared.

I’m an amazing programmer, a front-end developer.

If you are interested in me, please visit my personal website for further information.

  • If there are any errors in this article, please correct them in the comments section. If this article helped you, please like it and follow 😊
  • This article was first published in the magic programmer public number, without permission to prohibit reprinting 💌