This tutorial series is still under development
This is part 5 of the tutorial series Learn TypeScript in Construct. This part continues on from part 4. So in case you missed it, see Learn TypeScript in Construct, part 4: Control flow.
In this part we're going to continue working with the same project, but spend a little time to cover more about some more details about TypeScript's types that will come in handy in later parts.
Union types
Suppose you have a variable and you intentionally want to allow it to store a string or a number, but nothing else. TypeScript allows you to specify such a type using union types with the |
operator. Don't confuse it with the logical OR operator ||
that we used with boolean values - the |
operator has a special meaning when used as part of a type.
A variable can have a type like string
to mean it can only be set to a string. However a type like string | number
means it can be a string or a number.
// Declare a variable that can be a string or
// a number, and initialize it with a string
let value: string | number = "hello";
// This is now OK: it can be assigned a number
value = 123;
// This is an error: it still cannot be assigned
// a boolean, because the type does not include that.
value = true;
In short, union types allow you to specify a type that can be one of any given types separated by |
.
Type narrowing
Previously we briefly mentioned the typeof
operator. It will return the type of a value as a string, such as "number"
if the given value is a number.
TypeScript's type system is pretty clever, and it can use this to narrow types. This means if a value can be one of multiple types, you can use typeof
in an if
statement to check what type a value really is. Then TypeScript knows that inside the body of that if
statement, the type is actually the specific one you checked for. Here's an example.
// Declare a variable that can be either a string or a number
let value : string | number = "hello";
// Use typeof to check if the value is actually a string
if (typeof value === "string")
{
// Here TypeScript now knows that 'value' has the
// type 'string' only, and it cannot be a number
console.log("Value is string: " + value);
}
// Use typeof to check if the value is actually a number
else if (typeof value === "number")
{
// Here TypeScript now knows that 'value' has the
// type 'number' only, and it cannot be a string.
console.log("Value is number - multiplied by 2: " + (value * 2));
}
Note that normally multiplying a string by 2 is not allowed as it doesn't make sense to do that with a string. TypeScript will also disallow multiplying a value that could be a string, because that could be invalid. However if you use typeof
to check that the value really is a number and isn't a string, then TypeScript also then knows it really is a number, and so allows everything you can do with numbers, like multiplying them.
Literal types
You can also actually use a value as a type. This is particularly handy with strings. For example you can declare a variable with the type "red"
. That means the variable is only allowed to hold the specific string "red"
, and it cannot be anything else!
// The type of this variable is a string
let str : "red" = "red";
// Assigning it a different string is not allowed!
str = "blue";
This might seem a weird thing to do, but it becomes useful when combined with union types: you can declare a variable that can only have one of a specific set of strings.
// The type of this variable is any one of the strings
// "red", "green" or "blue"
let str : "red" | "green" | "blue" = "red";
// This is allowed now
str = "blue";
// This is still not allowed
str = "yellow";
This illustrates another benefit of TypeScript: in many cases if you make a typo and spell something wrong, JavaScript will just go ahead and run the code anyway and probably do something unexpected. However here if you did something like accidentally write "bleu" instead of "blue", TypeScript will actually mark it as an error, ensuring you fix it. That helps you enforce a rule like that a variable must be one of a specific set of strings. You could for example have a variable for an enemy's state, like "searching"
, "attacking"
and "running-away"
. TypeScript's type system can help make sure this rule is enforced.
Type aliases
Suppose you want to declare lots of variables with a type that is quite long, like the code snippet below.
let str1 : "red" | "green" | "blue" = "red";
let str2 : "red" | "green" | "blue" = "red";
let str3 : "red" | "green" | "blue" = "red";
It can be annoying to have to repeat the type "red" | "green" | "blue"
over and over again. To help avoid this TypeScript allows you to declare a custom type, also known as a type alias, like this:
type MyType = "red" | "green" | "blue";
Note this isn't declaring a variable. It's just declaring another kind of type you can use.
Now you can use MyType
anywhere you would use a type, and it means the same thing as "red" | "green" | "blue"
.
let str1 : MyType = "red";
let str2 : MyType = "red";
let str3 : MyType = "red";