Types
There is a standard Date
type built into Typescript. You can construct a Date
object using the new
keyword.
let now: Date = new Date();
Aliases
You can define an object type using a type
alias. The fields can seperated by ,
comma or a ;
semi-colon.
type Person = {
name: string,
age: number,
}
You can define optional object properties using a ?
question mark after the property name.
type Person = {
name: string,
age?: number,
}
If a value is not provided by a property defined as optional, then that property will be marked as undefined
. This is useful because it means a runtime error will not be triggered if the property is accessed.
You can alias union types.
type Id = number | string;
You can extend an existing type alias by creating a new type using an intersection. This will inherit properties from the existing type and allow you to add new properties.
type Dog = {
name: string,
woofsPerSecond: number,
}
type Robodog = Dog & { // type intersection here
operatingSystem: string,
}
const buzzo: Robodog = {
name: "Buzzo",
woofsPerSecond: 9000,
operatingSystem: "woofSX",
}
Interfaces
You can use interfaces to define object types.
interface Person {
name: string,
age: number
}
You can add properties to an interface by redefining it. It seems like this might also limit you from totally redefining an interface.
interface Person {
name: string,
}
interface Person {
age: numnber,
}
const person: Person = {
name: "Mike",
age: 444,
}
You can extend an interface using the extend
keyword to create a new interface that inherits the properties of another interface and adds new properties.
interface Dog {
name: string,
woofsPerSecond: number,
}
interface Robodog extends Dog {
operatingSystem: string,
}
const buzzo: Robodog = {
name: "Buzzo",
woofsPerSecond: 9000,
operatingSystem: "woofSX",
}
Aliases vs Interfaces
Generally aliases and interfaces can be used interchangeably. However there are some key distinctions.
Aliases are static and cannot be redefined.
type Animal = {
name: string
}
// Error: Duplicate identifier 'Animal'.
type Animal = {
petName: string
}
Interfaces cannot be used to alias primitives.
Type Assertions
If a particular complex type assertion is too constrained by the compiler but is known to be correct it can be solved by chaining an assertion an any
type and then to a target type.
const x = (result as any) as T;
Literals
Literal values use type hints which constrain the possible values that a type can take. For example you could constrain a variable to a single string.
let x: "hello" = "hello";
x = "bonjour" // ERROR: "hello" is not assignable to "bonjour"
This is also how const
values work but assinging a static type with a specific value. The above let
is equivalent to assigning x
to a const
of "hello"
.
You can constrain values using literal type unions so that only a specific set of values can be used.
type direction = "north" | "south" | "east" | "west";
let dir: direction = "north";
dir: direction = "up" // ERROR not assignable to type direction
Literals are not automatically inferred when defined inside objects. The properties val
and name
of the object below are inferred to be simply string
and number
and so can be mutated. They are not literals despite what defining a const
object with such “typehint-like” assignments might suggest.
const x = { val: 0, name: "Quizzle" };
There are are two ways to create object enclosed literals:
- Using the type assertions on the property when it is defined. This allows you to selectively choose which properties are literals.
const x = { val: 0, name: "Quizzle" as "Quizzle" }
- Using
as const
after the object assignment to make all properties literals that are immutable.const x = { val: 0, name: "Quizzle" } as const;
A full example of this behavior would be
const x = { val: 0, name: "Shima" };
x.val = 1;
x.name = "Buta";
const y = { val: 0, name: "Shima" as "Shima" };
y.val = 1;
y.name = "Buta"; // ERROR
const z = { val: 0, name: "Shima" } as const;
z.val = 1; // ERROR
z.name = "Buta"; // ERROR
Null
Non-null assertion operator
You can assert that a value is not null using the !
exclamation mark postfix operator. This circumnavigates the typescript compiler requiring null
checking in your code for values that know will not be null
and removes the null
type from the value. You need to be careful with this as it is just away to get around compiler restrictions rather than actually performing any null checking.
const testString = (x?: string | null ) => {
console.log(x!.toUpperCase());
}
testString("hello"); // => "hello"
testString(null); // ERROR
Unions
You can build up more complex types from previously defined types to create composite types, sort of like reverse inheritance where you move to a more general taxonomy of these from specific instances.
type Circle = { kind: "circle", radius: number };
type Square = { kind: "square", size: number };
type Shape = Circle | Square;
Discriminated Unions
The above Circle
and Square
types are an example of a discriminated union because there is a discriminator property, namely the kind
property on each object that can be used to discriminate between object’s of the same class. This is a kind of bottom up definition of types using composites of these two object schemas into the Shape
type (as opposed to a top down approach of something like inheritance). This allows you to write code like that below where a union with a common discriminating type can be called to narrow to a specific type that has specific values that are used.
const getShapeValue(shape: Shape) {
// shape.kind is callable because both objects in the union have this field
if (shape.kind == "circle") {
return shape.radius;
}
return shape.size;
}
Never
If all types in a statement are eliminated then the passed in type becomes never
which is also an error.