Chapter 3
Values, and the Types They Insist On
let, const, and why TypeScript won't guess quietly.
Last chapter you gave a value a name with let, and TypeScript quietly
decided what kind of value it was allowed to hold from then on. That was one
line, dropped in early because you needed it to see a type error happen.
Here’s the proper version.
let and const
let score = 7;
score = 9;
let declares a variable that can change. Make it, then replace what’s
inside it whenever you like, the second line doesn’t repeat the type or the
word let, that was only needed once, when the variable was created.
Some values shouldn’t change. A tax rate, a maximum, the number of cards in a
deck. Say so with const instead:
const taxRate = 0.2;
taxRate = 0.25;
npx tsc rate.ts
rate.ts(2,1): error TS2588: Cannot assign to 'taxRate' because it is a constant.
const is a promise you make to TypeScript and it enforces on your behalf.
It costs nothing to write and it turns “I’m fairly sure this doesn’t change”
into something checked. Default to const. Reach for let only once you
know a value actually needs to move.
Writing the type yourself
Last chapter, TypeScript worked out that score was a number from the 7
you gave it. You can also say so directly:
let score: number = 7;
A colon, then the type, sitting between the name and the value. For a line
like this one, writing : number doesn’t teach TypeScript anything it
couldn’t already see for itself, the value tells it everything. You’ll write
annotations like this constantly once you’re passing values into functions
in chapter 9, but for a plain variable with a value right there on the same
line, most TypeScript code leaves it out and lets the value speak for itself.
This book will do the same from here on, letting inference do the work
unless there’s a specific reason to spell a type out.
There is one moment an annotation earns its keep today: a variable with nothing in it yet.
The three types you’ll use constantly
| Type | Holds | Example |
|---|---|---|
number | Any number, whole or not | let score = 7; |
string | Text | let name = "Ada"; |
boolean | true or false, and nothing else | let ready = true; |
Three, not four. If you’ve seen another language split numbers into an
integer type and a decimal type, TypeScript doesn’t, 7 and 2.5 are both
just number. That simplicity has a cost, chapter 6 is where you’ll meet it.
Exercise 1 · Lie to it on purpose, this time with an annotation
Chapter 2 caused a type error by reassigning a variable. This time, cause one on the very line a variable is created, with an explicit annotation telling on you:
let price: number = "free";
let ready: boolean = "yes";Compile it and read both messages. They’re the same shape as chapter 2’s, because they’re the same underlying check, TypeScript comparing a value against a type it already knows, it just usually learns that type from the value rather than being told outright like this.
Giving TypeScript enough to work with
Chapter 2’s whole lesson depended on TypeScript knowing what score was
supposed to be. That knowledge has to come from somewhere, and if you don’t
give it anything, it stops checking rather than guessing:
let x;
x = 7;
x = "seven";
console.log(x);
npx tsc loose.ts && node loose.js
seven
tsc printed nothing, which means no error, and the program ran fine and
reassigned a plain number to a plain string along the way, with TypeScript’s
full blessing. Chapter 2 would have stopped you doing exactly this.
Give it something and the checking comes back, even without a value yet:
let score: number;
console.log(score);
npx tsc unset.ts
unset.ts(2,13): error TS2454: Variable 'score' is used before being assigned.
An annotation alone is enough for TypeScript to hold you to it. It won’t let
you read score until something has actually gone into it, not even to
print whatever garbage happened to be sitting there. That refusal is the
whole point, there’s no “probably a number”, there’s an error, on the spot,
naming the exact variable.
What you’re allowed to call things
Names can use letters, digits, underscores and $, and can’t start with a
digit. score, total_price and player2 are fine, 2ndPlace isn’t.
Case matters, score and Score are two different variables, which is a
fine way to confuse yourself at midnight.
Beyond the rules TypeScript enforces, use names that say what the thing is.
totalPrice beats tp, and it beats x by a mile. You’re writing for the
version of you that comes back in a month having forgotten all of this.
Check yourself
Project
A receipt
Roughly 30 minutes
Write a program that prints a receipt for a single item. It should hold, in properly named variables:
- the item’s name, as a
string - how many were bought, as a
number - the price of one, as a
number - a tax rate, as a
const, since it isn’t going to change while the program runs, so say so
Then work out the subtotal, the tax, and the total, and print something like:
RECEIPT
Mechanical keyboard
2 x 49.5
Subtotal: 99
Tax: 19.8
Total: 118.8Rules: every variable gets a value on the line that creates it, and
every name says what it holds. No x, no t2, and steer clear of name
for the reason above.
Stretch: add a second item with its own name, quantity, and price, and make the total cover both. You’ll notice you’re copying three variable declarations and changing the names. Hold that thought, it’s the itch chapters 9 and 12 scratch.