Basic Types

Use TypeScript, find enlightenment. Ignore TypeScript, trip over undefined.
- Ancient Chinese proverb

Annotating Variables

You can annotate variables by adding a type annotation:

let task: string = 'Read the Next.js book';

You can annotate constants in a similar fashion:

const name: string = 'Read the Next.js book';

Note that explicit type annotations are usually is not needed, since TypeScript can perform type inference tp automatically infer the type of a variable or a constant.

In this example, the variable name will automatically be inferred to have the type string:

let name = 'Read the Next.js book';

We will very rarely write explicit type annotations (only if we really need them) and instead let TypeScript infer as much as it can. You will be suprised by how few type annotations you need to get completely type checked code!

Primitive Types

The primitive types that are present in TypeScript should already be familiar to you from the chapter on JavaScript. TypeScript has the number type:

const id: number = 1;

TypeScript also has the string type:

const task: string = 'Read the Next.js book';

It also has the boolean type:

const inProgress: boolean = true;

Additionally TypeScript has the null and undefined types:

const undefinedTask: undefined = undefined;
const nullTask: null = null;

Remember that in a real codebase we would let TypeScript infer the variables:

const id = 1;
const task = 'Read the Next.js book';
const inProgress = true;
const undefinedTask = undefined;
const nullTask = null;

In this example, TypeScript will infer that id has the type number, task has the type string, inProgress has the type boolean, undefinedTask has the type undefined and that nullTask has the type null.

The any and unknown Types

TypeScript also has the any type. When a value is of the type any, you can access its properties, call it, or assign it freely, and basically do anything (get it?) that's syntactically correct. Also note that any property that you access will in turn have the type any.

Let's say you have a task variable of type any - then these are all legal:

let task: any;

console.log(task.title);
task();
task.thingy();

Basically using any is a way to tell the compiler to "shut up" and skip type checking altogether. This is also the reason why using any is generally a terrible idea since it defeats the purpose of using TypeScript in the first place!

However sometimes you do find yourself in a situation where you really don't know much or don't particularly care about a variable type. In this case it's better to use the unknown type.

Anything is assignable to unknown (just as with any), however no operations are allowed on an unknown variable without further assurances about the type of the variable.

For example, if you have a task variable of type unknown, then these are all no longer legal:

let task: unknown;

console.log(task.title);
task();
task.thingy();

Typing Arrays

You can type arrays as T[] where T is the type of the elements of the array. For example here is how you would type an array of numbers:

const evenNumbers: number[] = [1, 2, 3, 4];

Here is how you would an array of strings:

const tasks: string[] = ['First task', 'Second task', 'Third task'];

Note that for these simple examples we could again have TypeScript infer the types for use. In this example evenNumbers would have the type number[] and tasks would have the type string[]:

const evenNumbers = [1, 2, 3, 4];
const tasks = ['First task', 'Second task', 'Third task'];

Typing Objects

You can type an object by writing the property keys and types of its properties inside curly braces {}. For example here is how you could declare an object that has the properties id of type number, title of type string and description of type string:

const task: { id: number; title: string; description: string } = {
  id: 1,
  title: 'Read the Next.js book',
  description: 'Read and understand the Next.js book.',
};

As usual, you don't need to manually specify object types. In this example, task will be inferred to have the type { id: number; title: string; description: string }:

const task = {
  id: 1,
  title: 'Read the Next.js book',
  description: 'Read and understand the Next.js book.',
};

You can mark properties as optional by using the question mark ?. If a property is marked as optional, you can assign undefined to it or even not specify it altogether. For example, this is valid:

const task: { id: number; title: string; description?: string } = {
  id: 1,
  title: 'Read the Next.js book',
};

const task2: { id: number; title: string; description?: string } = {
  id: 1,
  title: 'Read the Next.js book',
  description: undefined,
};

Type Aliases

The object type syntax we just introduced is often quite verbose. This is why TypeScript gives you the possibility to specify a type alias (i.e. another name) for an object type:

type Task = {
  id: number;
  title: string;
  description: string;
};

Note that you can specify type aliases for more than just object types since type aliases are basically just different names for types. For example, we could specify a type alias for the primitive type string:

type ID = string;

Another important point to make about type aliases is that two type aliases are exactly the same as long as the underlying types are the same. For example this is valid even though it doesn't look like it:

type MyString = string;
const s1: MyString = 'My string';
const s2: string = s1;

This is because TypeScript uses a structural type system - it doesn't matter what the types are named (except for the primitive types of course), it only matters what their structure looks like.

Note that you can also use the interface keyword to achieve a similar effect:

interface Task {
  id: number;
  title: string;
  description: string;
}

We will stick to always using type. The interface keyword is out of scope for this book and will not be discussed further.

We briefly note that type and interface are not exactly the same and have a few subtle, but important differences. Again, we will not discuss this further.

Type Assertions

Sometimes you know more about the type of a variable than TypeScript. For example, let's say that you have a function that returns an any value, but for some reason you know that in fact that value is definitely going to be a string in your case. Then you can use a type assertion (also called a type cast) to force TypeScript to treat that value as a string.

Here is a (slightly contrived) example:

const str: any = 'This is a string';
const strLength = (str as string).length;

You should use type assertions extremely sparingly since you give up some of the benefits of using TypeScript. Usually there are better ways.