DEV Community

Cover image for The types in TypeScript
Chris Bongers
Chris Bongers

Posted on • Originally published at daily-dev-tips.com

The types in TypeScript

When it comes to TypeScript, a big part of the game is defining types.

With this, we can define annotations, but they can appear in more places.

In this specific article, we will go through the most basic types, and eventually, we'll dive a bit deeper into extended kinds.

The pillar of types

There are the primitive types that are very commonly used in JavaScript, basically responsible for most of your variables, and these three are:

  1. string: A string value
  2. number: A integer/number value, JavaScript doesn't care if it's an int or float. They call it a number
  3. boolean: The good old true or false

Besides these three pillars, you might need an array of certain elements.

Let's say an array of strings. We can use the bracket annotation for that: string[].

A tale of caution

When it comes to TypeScript, the default type will be used if you don't define something in particular.
This type is called any, and it could be anything.

You want to avoid using the any type when defining types.
You can even set the noImplicitAny flag to throw errors if any is used.

Using the types

Whenever you declare a variable or function, you can annotate the type by using a : {type} format.

Let's see how it would look for a variable and function:

let username: string = 'Chris';

const myName = (name: string) => {
  console.log(`Hello ${name}`);
};
Enter fullscreen mode Exit fullscreen mode

However, note that we don't explicitly have to mention a type on the' username' variable.
This is because TypeScript is smart enough to derive this as a string.

Let me show you what I mean by that:

TypeScript auto type

In the image above, you can see that we set the value as a string on the left and the right as a number.

Without explicitly telling a type, TypeScript knows what is going on.
This is only possible with variables that have a direct value!

We can also define the return type for functions.
We have a function that takes a number but returns a string.

const numberToString = (number: number): string => {
  return number.toString();
};

const output = numberToString(123);
Enter fullscreen mode Exit fullscreen mode

Note the : string behind the function, which is used to define a function's return type.

We already had a brief look at the array type. Another side pillar is the object annotation, defined by curly brackets.

const getFullName = (user: {firstname: string, lastname: string}): string => {
  return `${user.firstname} ${user.lastname}`;
};

getFullName({firstname: 'Chris', lastname: 'Bongers'});
Enter fullscreen mode Exit fullscreen mode

In the above example, the function accepts an object as the user variable. This object has two properties which both are strings.

Making types optional

Let's take the above example. There might be cases where we only know the first name and still want to call this function.
In our current implementation, it will throw a TypeScript error.

Type is missing

You can see that TypeScript states we are missing a required type of the last name.

We can prefix the : with a question mark to make a type optional.

const getFullName = (user: {firstname: string, lastname?: string}): string => {
  return `${user.firstname} ${user.lastname}`;
};
Enter fullscreen mode Exit fullscreen mode

It's important to note that by default, variables are required. We must explicitly mention which ones are optional.

What if my variable has multiple types?

This happens more often. Let's take an ID. For example, it could be a number or a string.

To define a type that has multiple, we have to use the union type.
You can define these union types using the pipe | option.

const getUserId = (id: number | string) => {
  return `Your ID is ${id}`;
};

getUserId(123);
getUserId('Chris123');
Enter fullscreen mode Exit fullscreen mode

As you can see, both use-cases are now valid.

However, what if we need to use a particular function that's not valid for one of the two?

We want to prefix the number IDs with a batch prefix, but the string versions already have this:

const getBatchString = (id: number | string): string => {
  if (typeof id === 'number') {
    id = `batch-${id}`;
  }
  return id;
};

getBatchString(123);
getBatchString('batch-123');
Enter fullscreen mode Exit fullscreen mode

In the above example, you can see that we can use typeof to determine which one of the two it is.

In the case of a number, we prefix it with a string. Otherwise, we return the string.

Both these use-cases will return batch-123.

And that's it for the basic types of TypeScript and how we can use them.

Thank you for reading, and let's connect!

Thank you for reading my blog. Feel free to subscribe to my email newsletter and connect on Facebook or Twitter

Latest comments (16)

 
siy profile image
Sergiy Yevtushenko

VIM has syntax highlight and autocompletion, like any other descent IDE.
Not every IDE and text editor have type hinting. Not every vim setup has even syntax highlighting and autocompletion, for example.

What I said is that an editor/IDE is as essential as version control
Which is not correct. Even FTP example provided by you is a (poor) variant of version control. CI builds your code without using editor/IDE. Same can do you from command line.

Stop trying to build that strawman out of "my taste", when I never said that.
Your opinion about essentiality of editor/IDE is not technically justified, so this is just your preference or just taste.

What I said is that anyone that codes for living uses an editor/IDE, and if you use that you get the benefits of coding without typing the return of functions.
And this is not correct for two reasons - editor/IDE not always available and not every editor/IDE has type hinting.

I'm arguing what you said that we need a return type to "keep the context", when in reality any descent code editor or IDE provides that context without the need of typing the return.
I already told you, that this is your project, your code, your rules. I'm not arguing with you. But for some reason you still arguing with me and trying to convince that your point of view (based on your taste - see above) is better than mine (technically justified and based on decades of experience). I really hope that you'll never by in situation when your economy of few typed characters will not bite you hard. That said I see no point continuing discussion. Good luck.

 
siy profile image
Sergiy Yevtushenko

You're still missing the point: there are essential tools and there are convenience tools. Version control, build tools and compiler, etc. are essential, you can't build software without them. IDE is a convenience tool. One can change such tools without affecting ability to build software. You prefer VSCode, somebody else prefers vim or some other IDE. It's not your decision what is productive to them.
Again, I see no point to argue and don't understand why you're trying to convince me that your taste is better than others.

 
siy profile image
Sergiy Yevtushenko

It's sad that you consider valid only your taste. Worse is that you telling me what I should do according to your inability to accept approaches different from yours. Just keep in mind that not everyone using your favourite IDE.

 
siy profile image
Sergiy Yevtushenko

I see no point to argue. I prefer to be explicit, preserve context and use language capabilities to express my intents. I believe that this is important, because my projects usually living many years and often it's not me who reads and changes my code and I can't make any assumptions in what conditions it will happen.
You prefer to rely on external, not essential and not always available tools and assume that you or someone, who will be reading your code, will have the same or similar non-essential tools. I see no problems with that. Your project, your code, your rules. I'm just trying to point that conditions could be different and your approach is not always applicable.

 
siy profile image
Sergiy Yevtushenko

You, seems, read my answer not carefully enough. There is often a need to read code not only in IDE.

 
siy profile image
Sergiy Yevtushenko

When you write code, you have a local context - business logic, algorithm, variables/parameters and their types. If you need to call some function, there is a narrower local context too - what you need to pass as parameters and what you'll get back. If you only writing code, often you need to take a look at function declaration to figure out what needs to be passed and what it will return. If return type is inferred, you have to look inside the function code to figure out the return type. The same happens when one reads the code (especially outside the IDE). Need to look inside the code to figure out something is a clear sign that part of context is lost. And lost context is a source of distraction and increased mental overhead. Brevity is good as long as it doesn't loss context.

Collapse
 
siy profile image
Sergiy Yevtushenko

Return type is part of the context, preserving it helps reading the code.

 
snigo profile image
Igor Snitkin • Edited

you're passing a function to a ReactNode, which can be a function

Exactly, but isn't this fact making situation worse?

 
snigo profile image
Igor Snitkin

What about this place ;)

<div>{numberToString(42)}</div>
Enter fullscreen mode Exit fullscreen mode
 
peerreynders profile image
peerreynders • Edited

I think your approach is heavily inspired by how folks solved stuff with classes.

Design by Contract is attributed to Bertrand Meyer in connection with the Eiffel OOPL (1986) but the "type first" notion appears elsewhere like in Clojure's "thinking in data, modeling in data, and working in data" which is most definitely not OO.

And the idea of "function types as contracts" is up-to-date - 3.7.1 Types and Contracts.

Explicit types serve as deliberate "check points" during type checking/linting.

Is faster because we focus on the implementation, and the types are derived from that.

Expedience is always fielded as the primary motivation for "implementation first, contract last" approaches. But more often than not, contract last approaches save you time in the short term but create problems (that could be avoided by actively managing your types) in the long term (TTIS - Time-To-Initial-Success).

To some degree it's like saying "I know that JSDoc annotations are helpful for maintenance but keeping them in sync with the code slows me down so it's not worth it". Types are part of the application's specification that are supposed make it easier to reason about the code, so it helps if they are not buried somewhere in the implementation.

I guess the lesson here is: just because TypeScript is being used doesn't actually reveal whether the potential of types is being leveraged to maximum effect (to the extend that is even possible in TypeScript).

Aside: