Reducing typing with Util Types in Typescript
In this publication, we will look at how you can reduce the number of types / interfaces in Typescript, make them more concise and save time for yourself and your team. We will do all this with Utility Types – special types provided by Typescript.
What are Util Types?
TypeScript provides utilities to facilitate type conversion. These types are available globally, add flexibility to type creation, and help maintain the DRY principle.
Optional fields in types – Partial
Let’s say we want to make all fields in a structure optional:
interface UserData {
uuid: string;
username: string;
age: number;
}
// Реализация такого типа ручками:
interface OptionalUserData {
uuid?: string;
username?: string;
age?: number;
}
transformation as in OptionalUserData
can be done with a special type Partial<T>
. All Util Types contain a generic to which we pass the type we need to convert.
Partial<T>
converts all fields in the type and makes them optional:
interface UserData {
uuid: string;
username: string;
age: number;
}
type OptionalUserData = Optional<UserData>; // Эквивалентно интерфейсу OptionalUserData, что был в листинге до этого
Make all fields required – Required
Required
is the opposite Partial
– it makes all fields required.
interface UserData {
uuid: string;
username: string;
age?: number; // Опциональное поле
}
type RequiredUserData = Required<UserData>;
// Property 'age' is missing in type '{ age: number; }' but required in type 'RequiredUserData'.
const person: RequiredUserData = {
uuid: "fff",
username: "tokiory"
};
Creating Strongly Typed Objects – Record
Record
is used to create strongly typed objects.
Type
T
needed to specify the key type for the object;Type
U
indicates the type of the field’s value.
interface PetInfo {
age: number;
breed: string;
}
type CatName = "lucas" | "oiko" | "jean";
const cats: Record<CatName, CatInfo> = {
lucas: { age: 1, breed: "Maine Coon" },
oiko: { age: 15, breed: "Persian" },
jean: { age: 16, breed: "British Shorthair" },
};
Important
If you specify instead CatName
in the example above string
, then you will lose Intellisense for this object in the editor. The fact is that Typescript will think that inside an object with type Record<string, ...>
you can put any string as a key and it won’t parse the keys you provided inside the object.
Making typed values with autocompletion
If we just create Record<string, ...>
then we will not see auto-completion.
Typescript as mentioned above will assume that Record<string, ...>
can have any field that falls under the type string
:
interface PersonInfo {
isOnline: boolean;
age: number;
}
type PersonalInfo = Record<string, PersonInfo>;
const persons: PersonalInfo = {
john12: {
isOnline: true,
age: 20
},
tokiory: {
isOnline: false,
age: 20
},
};
// Если мы напишем "persons." - то Intellisense не поймет чего мы от него хотим
Instead of this approach, we should use the operator satisfies
which was introduced in Typescript 4.9.
interface PersonInfo {
isOnline: boolean;
age: number;
}
type PersonalInfo = Record<string, PersonInfo>;
const persons = {
john12: {
isOnline: true,
age: 20
},
tokiory: {
isOnline: false,
age: 20
},
} satisfies PersonalInfo;
// Если мы напишем "persons." - то Intellisense заработает!!!
The idea is that the operator satisfies
will check each property and value from persons
for compatibility with source types in PersonalInfo
.
For example, field john12
is a special case of the type string
, satisfies
sees it
and understands that the key of the object satisfies that all keys must be of type string
and doesn’t throw any error.
It is important to note that, unlike the first approach, the second only checks for compatibility with the original types,
object types persons
not assigned, that is persons
has type:
const persons: {john12: {isOnline: boolean, age: number}, tokiory: {isOnline: boolean, age: number}}
Types for Functions – Parameters and ReturnType
These types are used to extract types from functions.
Parameters<T>
– returns an array of parameter types in the function;ReturnType<T>
– returns the return type of the function.
function capitalize({line}: {line: string}): string {
return s.length ? s[0] + s.slice(1) : s;
}
type CapitalizeFuncArgs = Parameters<typeof capitalize>; // [ { line: string } ]
type CapitalizeFuncReturn = ReturnType<typeof capitalize>; // string
Working with interface fields – Pick and Omit
Pick
And Omit
are mainly used with interfaces for convenient work with fields.
Pick<T, U>
– allows you to return one or more fields from the interface;Omit<T, U>
– allows you to return the entire interface without certain fields.
interface Person {
name: string;
surname: string;
age: number;
login: string;
email: string;
}
type LoginInfo = Pick<Person, "login" | "email" | "name">; // => { login: string; email: string; name: string; }
type ShowName = Omit<Person, "login" | "email">; // => { name: string; surname: string; age: number; }
Common Elements and Unique Elements – Extract and Exclude
Extract
– returns all types that are in one and the other passed union of types;Exclude
– returns all types that are either in the first or in another type union passed;
type MaleName = "Carl" | "Christian" | "Daren";
type FemaleName = "July" | "Daren" | "Ann";
type OnlyMaleName = Exclude<MaleName, FemaleName>; // "Carl" | "Christian"
type OnlyGeneralName = Extract<MaleName, FemaleName>; // "Daren"
Interaction with String Types
Below are provided types for interacting with string types:
Uppercase<StringType>
– A type that converts all characters to uppercase;Lowercase<StringType>
– A type that converts all characters to lower case;Capitalize<StringType>
– A type that converts the first character to upper case;Uncapitalize<StringType>
– A type that converts the first character to lower case.
type Cat = "cAt";
type UpperCat = Uppercase<Cat>; // "CAT"
type LowerCat = Lowercase<Cat>; // "cat"
type CapitalCat = Capitalize<Cat>; // "CAt"
type UncapitalCat = Capitalize<Cat>; // "cAt"
If you pass data an allied type, then Typescript will process the type for each element of the union:
type Pet = "doG" | "CaT";
type UpperCat = Uppercase<Cat>; // "DOG" | "CAT"
type LowerCat = Lowercase<Cat>; // "dog" | "cat"
type CapitalCat = Capitalize<Cat>; // "DoG" | "CaT"
type UncapitalCat = Capitalize<Cat>; // "doG" | "caT"
Instead of a conclusion
If you liked this article, then you can always go to my blogthere is more related information about web development.
If you have any questions – feel free to ask them in the comments. Have a good time! 💁🏻♂