Developed By
Gautam Kumar - Full stack developer
DEEP DIVE INTO
Utility types in JavaScript, often associated with TypeScript, are predefined type constructs that simplify and enhance the type system of the language. These utility types are used to create new types or modify existing types.
While TypeScript has a rich set of utility types, pure JavaScript does not have built-in utility types like TypeScript. However, you can create similar functionality in JavaScript using functions and custom code.
Below, we'll take a deep dive into some common TypeScript utility types and explore how you can implement them in JavaScript:
The Partial<T>
utility type creates a new type with all properties of the type T set as optional. This is particularly useful when you want to create a type that allows for some or all of its properties to be optional.
javascripttype PartialPerson = Partial<{ name: string, age: number }>;
// Usage
const person: PartialPerson = {};
person.name = "John";
javascriptfunction partial(obj) {
return Object.keys(obj).reduce((acc, key) => {
acc[key] = undefined;
return acc;
}, {});
}
const partialPerson = partial({ name: undefined, age: undefined });
// Usage
partialPerson.name = "John";
The Readonly<T>
utility type creates a new type with all properties of the type T set as read-only, preventing any modification after initialization.
javascripttype ReadonlyPerson = Readonly<{ name: string, age: number }>;
// Usage
const person: ReadonlyPerson = { name: "John", age: 30 };
// This will cause a compile-time error:
// person.name = "Jane";
javascriptfunction readonly(obj) {
return new Proxy(obj, {
set(target, prop, value) {
throw new Error(`Cannot set property '${prop}' of a readonly object.`);
},
});
}
const readonlyPerson = readonly({ name: "John", age: 30 });
// Usage
console.log(readonlyPerson.name); // "John"
// This will throw an error:
// readonlyPerson.name = "Jane";
The Record<K, T>
utility type creates a new type with keys of type K and values of type T. It is helpful when you want to define objects with specific keys and a uniform value type.
javascripttype RecordOfPeople = Record<"Alice" | "Bob" | "Charlie", { age: number }>;
// Usage
const people: RecordOfPeople = {
Alice: { age: 25 },
Bob: { age: 30 },
Charlie: { age: 35 },
};
javascriptfunction createRecord(keys, valueFactory) {
return keys.reduce((acc, key) => {
acc[key] = valueFactory(key);
return acc;
}, {});
}
const recordOfPeople = createRecord(["Alice", "Bob", "Charlie"], (name) => ({ age: undefined }));
// Usage
recordOfPeople.Alice.age = 25;
The Pick<T, K>
utility type creates a new type with a selection of properties K from the type T. This is useful when you want to extract a subset of properties from a larger type.
javascripttype Person = { name: string; age: number; address: string };
type PersonInfo = Pick;
const person: PersonInfo = { name: "John", age: 30 };
person.address = "New York"; // Error: Property 'address' does not exist on type 'PersonInfo'
javascriptfunction pick(source, keys) {
const result = {};
for (const key of keys) {
if (key in source) {
result[key] = source[key];
}
}
return result;
}
const person = {
name: "John",
age: 30,
address: "New York",
email: "john@example.com",
};
const pickedProperties = pick(person, ["name", "age"]);
console.log(pickedProperties); // Output: { name: 'John', age: 30 }
The Omit<T, K>
utility type creates a new type that omits a selection of properties K from the type T. This is the opposite of Pick and is useful when you want to exclude specific properties from a type.
javascripttype Person = { name: string; age: number; address: string };
type PersonProfile = Omit;
const person: PersonProfile = { name: "John", age: 30 };
person.address = "New York"; // Error: Property 'address' does not exist on type 'PersonProfile'
javascriptfunction omit(source, keysToOmit) {
const result = { ...source };
for (const key of keysToOmit) {
if (key in result) {
delete result[key];
}
}
return result;
}
const person = {
name: "John",
age: 30,
address: "New York",
email: "john@example.com",
};
const omittedProperties = omit(person, ["address", "email"]);
console.log(omittedProperties); // Output: { name: 'John', age: 30 }
The Exclude<T, U>
utility type creates a new type by excluding all properties that are assignable to U from the type T. Conversely, Extract<T, U> creates a new type by including only properties that are assignable to U.
javascripttype AllColors = "red" | "blue" | "green" | "yellow";
type PrimaryColors = "red" | "blue";
type NonPrimaryColors = Exclude; // "green" | "yellow"
type OnlyPrimaryColors = Extract; // "red" | "blue"
The extract
function can be used to create a new object with only the specified properties extracted from an existing object. Here's a simple implementation:
javascriptfunction extract(source, keys) {
const result = {};
for (const key of keys) {
if (source.hasOwnProperty(key)) {
result[key] = source[key];
}
}
return result;
}
const person = {
name: "John",
age: 30,
city: "New York",
email: "john@example.com",
};
const extracted = extract(person, ["name", "age"]);
console.log(extracted); // Output: { name: 'John', age: 30 }
In this example, the
function takes an object and an array of property names to be extracted. It creates a new object with only the specified properties from the source object.extract
The Required<T>
utility type creates a new type with all properties of the type T marked as required, effectively reversing the behavior of Partial.
javascripttype PartialPerson = { name?: string; age?: number };
type RequiredPerson = Required;
const person: RequiredPerson = { name: "John", age: 30 };
javascriptfunction required(source, keys) {
const missingKeys = keys.filter((key) => !source.hasOwnProperty(key));
if (missingKeys.length > 0) {
throw new Error(`The following properties are required: ${missingKeys.join(', ')}`);
}
return source;
}
const person = {
name: "John",
age: 30,
};
required(person, ["name", "age"]);
console.log(person); // Output: { name: 'John', age: 30 }
These are simplified JavaScript implementations of some TypeScript utility types. While TypeScript's type system offers strong static typing and validation, you can achieve similar functionality in JavaScript using custom functions and patterns, albeit without the same level of compile-time checking. These utility types can help you write more robust and predictable code in both JavaScript and TypeScript.