ALL POSTSTypeScript
All of my published content
Define a Union Type from an Object
Typing a Data Layer - Example
An example of typing a data layer. This code defines a TypeScript interface EntityMap that acts as a type lookup table. It maps string keys (e.g. "book", "movie", "song") to class instances. There is also a Store class, which provides a generic CRUD API for accessing and managing instances of these classes.
Filtering Properties by Value
Here's a detailed explanation of how to filter properties based on the value type in TypeScript using the built-in `Pick` utility. There's an example of hardcoded filtering using the Document object and a more generic version using type parameters. The explanation breaks down the steps of filtering using mapped index types, conditional types, never, index access types, and the intersection operator.
Mapped Index Types
TypeScript allows you to define new types based on existing ones using Mapped Types and Indexed Access Types. The mapped type creates a new type by looping over a set of keys and determining the value type for each key based on the original type. The built-in `Pick` type utility allows you to select a set of properties from an original type and use them to create a new type. Additionally, TypeScript provides three utility types `Partial`, `Required`, and `Readonly` to modify properties in a type as optional, required, or readonly respectively. You can also define your own utility types for specific use cases.
Template Literal Types
Template literal types are a way to create a new type in TypeScript by using the same syntax as JavaScript template literals, but in a type expression rather than a value expression. Types can be created by combining two union types of string literals. TypeScript also provides utility types such as `UpperCase`, `LowerCase`, `Capitalize`, and `Uncapitalize` that can be used within the template literal types. An example of using a template literal type would be creating a custom type to represent setter methods of a data state object.
Indexed Access Types
Indexed Access Types are a way to retrieve specific property types from another type in TypeScript. They allow for accessing parts of a type by using a property key or index, making it easy to extract specific parts of an object or array.
Mapped Types
Mapped Types are used to specify a specific subset of keys and allow us to transform types in a structured and deliberate way. Mapped Types are similar to the map function for arrays. The TypeScript utility Record is an example of a mapped type.
Inference with Conditional Types
Conditional types in TypeScript can be used with the `infer` keyword to extract type information from other types. The `infer` keyword is used within the condition expression of a conditional type and can only be used in that context. An example use case of `infer` is to determine the type of the first argument of a class constructor.
Conditional Types
Conditional types use a ternary syntax to express types, where the condition determines the type. The use of the `extends` keyword acts as a minimum requirement or a generic constraint, similar to a `>=` comparison.
Extract and Exclude Utilities
The `Extract` and `Exclude` utility types in TypeScript are used to obtain sub-parts of a type. The `Extract` type is used to get a sub-part of a type that is assignable to some other type, while the `Exclude` type is used to obtain a sub-part of a type that is not assignable to some other type. Both types are implemented using conditional types. They are useful for refining and narrowing down the properties of complex types in TypeScript.
Type Queries
The keywords `keyof` and `typeof` are type queries in TypeScript that allow you to obtain type information from values. `keyof` is used to obtain types representing all property keys on a given interface. `typeof` is used to extract a type from a value. When used with classes, `typeof` is commonly used to obtain a type representing the constructor (or static side) of a class.
Classes and TypeScript
For classes, the TypeScript programming language provides three access modifier keywords to describe who should be able to see and use class fields and methods. The keywords are `public`, `protected`, and `private`. Public allows access to instances of the class and subclasses, protected allows access only within the class and subclasses, and private only allows access within the class itself. Additionally, the `readonly` keyword can be used to prevent reassignment of a property and the param properties provide a more concise syntax for class definition.
Generic Constraints and Scope
Generic constraints allow for a minimum requirement to be set for type parameters, providing a high level of flexibility while ensuring minimal structure and behavior. Type parameters in TypeScript work similarly to function parameters, where inner scopes can access outer scopes, but not vice versa.
Generics - Examples
Examples of higher-order functions (map, filter, reduce) that operate on dictionaries, using Generics.
Generics
Generics are a way of defining types in terms of other types in TypeScript. They are type parameters and are used to establish a relationship between the type of thing being passed to a function and the type of thing returned from the function. Generics are similar to function arguments, but for types, and their type will change based on the type parameters provided.
Nullish Values
In TypeScript the nullish values are `null`, `undefined`, `void`, and the non-null assertion operator `!`. `null` means there is a value and that value is nothing, while `undefined` means the value isn't available (yet?). `void` is used to describe that a function's return value should be ignored. Finally, the non-null assertion operator (!) is used to cast away the possibility that a value might be null or undefined, but it is not recommended to use it.
Top and Bottom Types
The Top Types in TypeScript describe a set of values that can be any possible value allowed by the type system. The two main Top Types are `any` and `unknown`. `any` can accept any value but may cause runtime errors. `unknown` can accept any value but requires a type guard and is considered a safer option for values received at runtime. The Bottom Type is `never`, which describes no possible value allowed by the type system and is typically used for exhaustive conditionals where all possible types have been checked and the only remaining type is `never`.
Type Guards and Narrowing
Type guards are used in TypeScript to determine the type of a value and narrow it down to a more specific type based on certain conditions. There are built-in type guards like `instanceof`, `typeof`, and `Array.isArray()`. User-defined type guards can be created with the `is` operator, which is used in a type guard function with a return type of `valueToTest is Type`. To write high-quality type guards, it is important to be careful when checking for falsey values as it could lead to incorrect type castings.
Function Overloads
Function Overloads are a solution in TypeScript for resolving similar issues as Discriminated Unions. The idea is to have the type of one parameter determine the type of another parameter in a function. This is done by declaring multiple "heads" that define an argument list and return type, followed by the implementation. The implementation must be general enough to include all the possibilities from the "heads".
Function Types
There are two different approaches to defining function types in TypeScript. The first approach uses a type alias to specify the input and return types of a function, while the second approach uses an interface. Also note the `void` type, which is used to describe the return type of a function whose return value is intended to be ignored. Setting a function's return type to `void` can help prevent errors.
JSON Types
A JSON type represents any allowable JSON value, which must be an object, array, number, string, boolean, or one of the literal names false, true, or null. It cannot be a function, undefined, BigInt, or class. The type is defined using recursive types.
Type Aliases and Interfaces
Type aliases can only be declared once within a scope and can hold any type. Interfaces, on the other hand, are used to define object types and can be extended or implemented by classes. TypeScript interfaces are open, meaning multiple declarations can be made in the same scope and are merged together. It's best to use a type alias if you need to define something other than an object type and an interface if you need to define an object type that can be extended or implemented by a class.
Union Types
Union types are described using the `|` (pipe) operator and can be thought of as the logical boolean operator `OR`. The possible overlap of types in the union is the shared properties of all objects in the union. Here we explain how to use type guards and discriminated unions to determine the exact type of a value in a union type. These guards use expressions or a property of an object to identify the specific type.
Excess Property Checking
TypeScript helps detect an issue in the use of object literals by checking for excess properties in comparison to a defined type. If an object literal is used as an input to a function, TypeScript throws an error if there are any excess properties. The error can be fixed by either removing the property, adding the property to the function argument type or passing the object as a variable.
Function Return Types
It can be important to explicitly declare function return types in TypeScript. The return type declaration helps TypeScript verify that the implementation matches the expectation and improves error notification by surfacing errors where the function is defined rather than where it is invoked. It's best to be explicit about the return type to avoid issues with possibly undefined values.
Index Signatures
When accessing properties from an object typed with an index signature, we need to be careful as the index signature allows us to assume any property and provides it with type completions, even if the constant phones has been assigned to an empty object. A safer implementation would be to first check if the property is a defined property on the object by using an if statement or the optional chaining operator.
Optional Properties
In TypeScript, optional properties can be left out and will have a type of `undefined`. This is only true if the property is marked as optional with a `?` in the type definition. If the property is not marked as optional, it must be included in the object passed to a function and cannot be left out. The type of an optional property is more than just `type | undefined`.
Tuples
A tuple in TypeScript is a data structure that consists of an ordered list of elements with a specific type. To declare a tuple, you need to explicitly state the type of each element in the list. As of TypeScript 4.3, the support for enforcing tuple length constraints is limited.
Type Inference and Literal Types
The basics of TypeScript's handling of type inference and literal types. Using "let" infers a general type, while "const" infers a specific, immutable literal type. Objects properties are non-literal by default, but can be made literal with "as const".
Type Systems
Type checking is a task that evaluates compatibility or type equivalence between a function's input and a provided argument. It also applies to variable assignments and return types. TypeScript uses a structural type system, meaning that it only cares about the shape or structure of the argument, not its origin. TypeScript is also a static type system, meaning it performs type checking at compile time, while dynamic type systems perform type checking at runtime.