1/ Everything is a Type
The foundation of TypeScript is that everything — absolutely every variable, constant, or object — is a type.
You will always be declaring types, and chasing down the type declaration of your variables, constants, and objects to understand how to work with them.
Declaring your types means telling the TypeScript compiler, “I know what type of thing this variable is.” Unlike duck-typed and loosely-typed languages, a strongly typed language like TypeScript means you should always have confidence that a variable is what it says it is.
Indeed, In the case when you don’t know what an input might be, TypeScript also has mechanisms for that. Except when you receive data from external inputs, you can be assured that the TypeScript compiler means this is true (More on that in section 4).
Variable, Constant, and Object Declaration
A type declaration is really easy to spot: It is just like the Javascript you already know (var
, let
, const
), but has two extra elements: a colon (:
) and a type declaration.
When you first declare a variable, constant, or object within the current scope (remember, it’s just Javascript, so all of the scoping rules of Javascript you know also apply), you declare the name of the variable or constant to the left of the colon and the type to the right of the colon.
var foo : string;
Create variable foo
(in local scope) and tell the TypeScript compiler it is a String. The only exception is when assigning a variable to a literal, TypeScript can infer the type declaration for you.
foo = "Hello world";
Here, TypeScript already knows that "Hello world"
is a string (it knows this because quotation marks surround it. We call this a string literal.) Likewise, we assign the string literal directly onto the variable foo
when we declare it. We can leave the type definition off completely and rely on “good old Javascript” syntax.
This assumes you have the noImplicitAny
setting set to true (see below), which is strongly recommended.
Don’t confuse with Javascript Destructure & Rename
Now that you’ve seen the Typescript syntax using colons (the variable to the left of the colon and the type to the right) note not to confuse this syntax with colons used in Javascript destructing that are not type definitions. When you see colons in Javascript destructuring, this indicates a special kind of destructuring called “destructure & rename.”
const {
foo: myFoo,
loading: isLoading,
} = myData;
Here, the shape of myData
will contain a key foo
and another key loading
. But in the destructure & rename syntax above, we both destructure foo
and loading
out of myData and also rename foo to myFoo
and loading to isLoading
. That means that there will be local constants called myFoo
and isLoading
(not foo and loading).
It’s easy to see colons in Javascript/Typescript code and think you’re looking at type declarations. While that’s true much of the time, if you see colons inside a destructuring syntax, it’s not a type definition.
Function Parameter Declaration
All functions which take inputs in TypeScript have declared types. Hence, that means that when you declare a function you will also declare types for the inputs (variables) passed to the function. This is referred to as “function type declaration” and looks like this:
Return Value Declaration
All functions which return a value (if they return a value), that has a declared type return value. Indeed, this means that when you are working with TypeScript, the compiler checks your type declarations. How does it do this?
It uses a Typescript compiler, which we will configure in the next section. It can do this because of two things: (1) An integration with your IDE (VSCode, WebStorm, or Rubymine) or powerful shell editor like vim means that when the coding tool itself runs, and (2) the settings specified in your Typescript config file tsconfig.json
, which lives at the root of your project and specifies important things like the target Javascript version and what rules TypeScript should be used for your project.
The code you write is always checked in the background for TypeScript problems, which are typically type declaration mismatches.
2/ Getting TypeScript
To install tsc
for the first time (the command line tool used to work with TypeScript), you must first install it using Yarn or NPM.
If you use yarn, run:
yarn global add tsc
If you use npm, run:
npm install -g tsc
Alternatively, on a Mac, you can also install it globally using Homebrew, like so:
brew install typescript
Check to make sure you have the tsc
command install using which tsc
which tsc
If you see the path to the tsc
command, you are OK to continue. If you see en error “tsc not found
” you do not have the command correctly installed.
…
…
3/ TypeScript Config
Let’s do our first TypeScript experiment and learn how to set up our TypeScript config. The Typescript config is the all-important configuration file that tells TypeScript exactly what to do. Since TypeScript has several options, this file is very important to understand. This crash course will cover only the first few and most important options for setting up new projects.
Create a new empty project using mkdir HelloTS
Then cd HelloTS
to change the directory into that directory.
Then use tsc --noImplicitAny --init
to create a new basic .tsconfig
file:
Let’s take a look at our new file in our code editor:
Notice that nearly every option is commented out (shown as light grey), which means it won’t be applied.
Target
The first option not commented out is the target
, which specifies which version of Javascript TypeScript will compile to. For compatibility with the oldest browsers, you’ll need to use ES5. But if you want compatibility with only new browsers, you can set this to a newer version of JavaScript (or ECMAScript, JavaScript’s technical name). Here by default, we are compiling to ES5.
Module Style
The module
setting specifies which style of module declaration to use: AMD or commonjs.
Strict
Next is the strict
mode setting, which we have set here to true. By default, if you have strict mode enabled, all of the “strict” settings will also be enabled (even if they are commented out in the .tsconfig
file):
- noImplicitAny
- strictNullChecks
- strictFunctionTypes
- strictBindCallApply
- strictPropertyInitialization
- noImplicitThis
- alwaysStrict
noImplicitAny
We haven’t learned what the “any” type is at all (more on that in Lesson 6), but this tells TypeScript that all variables, constants, and objects must have known Types (that is, we must declare them or let TypeScript infer them when we write our code.) This is the fundamental paradigm of TypeScript itself and how we introduced TypeScript above, so it is presented here as the default setting.
strictNullChecks
If you set this to true (recommended), null
and undefined
will have their own distinct types. You’ll get a type error if you try to use them where a concrete value is expected. If set to false, null and undefined are ignored by Typescript, leading to unexpected errors.
Remember, these are just some of the options available. See the documentation on tsconfig for more info.
Should I use strict mode in TypeScript or not?
Strict mode in TypeScript is strongly recommended if you start a new project. It will help you to avoid errors, typos, and mistakes in your code.
Strict mode constricts you in how you can write your code.
If you want strict mode settings, you can opt-in parameter by parameter. (For example, if you have an existing codebase you want to convert over into TypeScript.)
You probably won’t be able to set the parameter strict
to true in an existing project. But you can set some strict mode parameters to rewrite your codebase using strict mode rules gradually.
For example, you can set the parameter noImplicitAny to true and rewrite your code to one that follows this rule (you should write a type of parameter in every function).
4/ Compiled vs. Executed
One of the key things to understand about TypeScript is that type-checking happens when your code is compiled, not necessarily when it is executed.
The only time a static type is checked in TypeScript is during compilation.
The resulting JavaScript doesn’t know anything about the types you’ve created.
This means you aren’t guarding against the kind of invalid input you may encounter from an external source.
By default, TypeScript does not verify types at runtime. This avoids the runtime overhead and also aggressively optimizes runtime performance.
Contextual information about types is removed during compilation because we are compiling to pure JavaScript. Because the type checking is not as strict in JavaScript as it is in other languages, JavaScript doesn’t know the types at runtime.
So how do you enforce that a thing is the correct type at runtime?
- TypeScript type guards: Type guards restrict the scope of variable types through conditional blocks and determine the type of variable to expect during code execution
- Validation libraries: These libraries provide ready-made boilerplate code, methods, and interfaces to perform schema validation for your variable types
- JSON schemas: The popular data interchange format for fast, easy, lightweight, and quick data transfers and conversion for the different entities to communicate.
- Manual checks: Maintaining and applying the set of rules on a specific language to validate the types of data values is a cumbersome way of single-handedly managing rules with no other runtime type-checking approach or library used, eventually creating an unstable system.
5/ any
: The Godfather Type
• Any is a last resort; you should avoid it when possible.
• Using any
means you are not using Typescript, as a variable with a type of any will behave like regular Javascript
• Don’t use any
unless you have no other choice.
• any
makes your variable behave like regular Javascript, preventing the type-checker from working.
6/ unknown
: The Mystery Man Type
• Like any
, it represents any value, but it must be refined before you can use it. That way, Typescript treats it like any until you refine it; then, once you do, it becomes a proper type.
• Typescript will never infer something is unknown; you have explicitly stated it is unknown.
• You can compare values to values of two unknowns.
• You can’t do things to it that assume it is a specific type (like concatenating a string or adding numerical values).
7/ null
, undefined
, never
8/ Other Basic Types
Boolean type
• Unlike other languages, is it recommended to avoid casting another value into a boolean.
• You will often be telling typescript something is not just a boolean, but specifically true or false. This is called a type literal. A type literal represents as single value and nothing else.
Number
Bigint
String
9/ Symbols & Objects
Symbols
Objects
10/ Aliases, Unions, Intersections
Alias
Union
Intersection
11/ Array Type
12/ Tuple type
13/ Enums
14/ Generic Types
15/ Classes & Interfaces
16/ Functions and Return Types
17/ Subtypes and Supertypes
18/ Type Widening
19/ Escape Hatches
Unypted Javascript
You’ll need to use an escape catch when dealing with untyped Javascript. For example, if you’re loading a JS library using CommonJS (require(...)
syntax), you’ll need an escape hatch. You’ll also need this if you’re loading Javascript via a CDN, as is commonly found in tutorials and learning materials.
In the case of the Plaid link library (as an example) the docs explicitly provide the library in two ways: (1) Using a React version of the library, and (2) Using a CDN via Javascript.
If you aren’t writing a React app but are still using Typescript, because the library isn’t provided as Typescript at all, here you must use an escape hatch.