Learn TypeScript w/ Mike North

Variables and Values

June 08, 2021

Table of Contents

Now that we have compiled a simple TypeScript program, let’s look at the basics of the programming language.

Variable Declarations & Inference

In JavaScript we declare variables all the time with let and const like this:

ts
let age = 6
let age: number
Try

As we can see, TypeScript is able to infer that age is a number, based on the fact that we’re initializing it with a value as we are declaring it.

If we try to give age a value that is incompatible with number, we get an error

ts
let age = 6
age = "not a number"
Type 'string' is not assignable to type 'number'.2322Type 'string' is not assignable to type 'number'.
Try

In TypeScript, variables are “born” with their types. Although there are ways of making them more specific in certain branches of code, there’s no (safe) way of changing age’s type from number to string.

Let’s try the same thing with const:

ts
const age = 6
const age: 6
Try

Notice that the type of this variable is not number, it’s 6. TS is able to make a more specific assumption here, because:

  • const variable declarations cannot be reassigned
  • the initial value assigned to age is a number, which is an immutable value type

Therefore, age will always be 6 in this program.

Literal Types

The type 6 is called a literal type. If our let declaration is a variable that can hold any number, the const declaration is one that can hold only 6 — a specific number.

emoji-bulb Theme: Inferring with safe specificity

There’s a common idea you’ll see again and again when working with TypeScript. Inference is not so specific as to get in the way of common behavior.

For example, the let variable declaration above could have assumed age to be of type 6, but this would have interfered with our ability to set this re-assignable variable to 7 or 8.

Implicit any and type annotations

Sometimes, we need to declare a variable before it gets initialized, like endTime below:

ts
// between 500 and 1000
const RANDOM_WAIT_TIME =
Math.round(Math.random() * 500) + 500
 
let startTime = new Date()
let endTime
let endTime: any
 
setTimeout(() => {
endTime = 0
endTime = new Date()
}, RANDOM_WAIT_TIME)
Try

endTime is “born” without a type, so it ends up being an implicit any.

TypeScript doesn’t have enough information around the declaration site to infer what endTime should be, so it gets the most flexible type: any.

Think of any as “the normal way JS variables work”, in that you could assign endTime to a number, then later a function, then a string.

If we wanted more safety here, we could add a type annotation:

ts
// between 500 and 1000
const RANDOM_WAIT_TIME =
Math.round(Math.random() * 500) + 500
 
let startTime = new Date()
let endTime: Date
let endTime: Date
 
setTimeout(() => {
endTime = 0
Type 'number' is not assignable to type 'Date'.2322Type 'number' is not assignable to type 'Date'.
endTime = new Date()
}, RANDOM_WAIT_TIME)
Try

Now, TypeScript will correctly alert us when we try to flip flop between the number 0 and a Date.

Function arguments and return values

The : foo syntax we’ve just seen for variable type annotations can also be used to describe function arguments and return values. In this example it’s not clear, even from the implementation of the function, whether add should accept numbers or strings.

ts
function add(a, b) {
return a + b // strings? numbers? a mix?
}
Try

Here’s what your in-editor tooltip would look like if you were using this function:

ts
const result = add(3, "4")
function add(a: any, b: any): any
result
const result: any
Try

Without type annotations, “anything goes” for the arguments passed into add. Why is this a problem?

ts
const result = add(3, "4")
const p = new Promise(result)
const result: any
Try

If you’ve ever created a Promise using the promise constructor, you may see that we are using a string where we should use a two-argument function. This is the kind of thing we’d hope that TypeScript could catch for us.

Without type annotations, “anything goes” for the arguments passed into add. Why is this a problem?

Let’s add some type annotations to our function’s arguments:

ts
function add(a: number, b: number) {
return a + b
}
const result = add(3, "4")
Argument of type 'string' is not assignable to parameter of type 'number'.2345Argument of type 'string' is not assignable to parameter of type 'number'.
Try

Great, now we can enforce that only values of type number are passed into the function, and TS can now determine the return type automatically:

ts
function add(a: number, b: number) {
return a + b
}
const result = add(3, 4)
function add(a: number, b: number): number
Try

If we wanted to specifically state a return type, we could do so using the :foo syntax in one more place

ts
function add(a: number, b: number): number {}
A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.2355A function whose declared type is neither 'undefined', 'void', nor 'any' must return a value.
Try

This is a great way for code authors to state their intentions up-front. TypeScript will make sure that we live up to this intention, and errors will be surfaced at the location of the function declaration instead of where we use the value returned by the function.



© 2023 All Rights Reserved