TypeScript Notes
đź“– tl;dr: Dump of Notes for TypeScript
-
Your existing working JavaScript code is also TypeScript code.
-
TypeScript knows the JavaScript language and will generate/infer types for you in many cases.
let helloWorld = "Hello World";
-
You can explicitly describe this object’s shape using an interface declaration:
interface User { name: string; id: number; }
-
You should prefer
interface
. Usetype
when you need specific features. -
Generics provide variables to type:
type StringArray = Array<string>; type NumberArray = Array<number>; type ObjectWithNameArray = Array<{ name: string }>;
-
One of TypeScript’s core principles is that type checking focuses on the shape that
value
s have. This is sometimes calledduck typing
orstructural typing
. -
There is no difference between how classes and objects conform to shapes:
class VirtualPoint { x: number; y: number; constructor(x: number, y: number) { this.x = x; this.y = y; } } const newVPoint = new VirtualPoint(13, 56); logPoint(newVPoint); // logs "13, 56"
-
TypeScript act a bit more strictly. In that case, you can use the
noEmitOnError
compiler option. -
That’s why TypeScript needs a compiler in the first place - it needs some way to strip out or transform any TypeScript-specific code so that you can run it.
-
Using any often defeats the purpose of using TypeScript in the first place. The more typed your program is, the more validation and tooling you’ll get.
-
Turning on the
noImplicitAny
flag will issue an error on any variables whose type is implicitly inferred as any. -
The type names
String
,Number
, andBoolean
(starting with capital letters) are legal, but refer to some special built-in types that will very rarely appear in your code. Always usestring
,number
, orboolean
for types. -
To specify the type of an array like
[1, 2, 3]
, you can use the syntaxnumber[]
. -
Note that
[number]
is a different thing; refer to the section onTuples
. -
When you don’t specify a type, and TypeScript can’t infer it from context, the compiler will typically default to
any
. Becauseany
isn’t type-checked. Use the compiler flagnoImplicitAny
to flag any implicitany
as an error. -
TypeScript doesn’t use types on the left style declarations like
int x = 0;
Type annotations will always go after the thing being typed. -
const names = ["Alice", "Bob", "Eve"]; // Contextual typing for function - parameter s inferred to have type string names.forEach(function (s) { console.log(s.toUpperCase()); }); // Contextual typing also applies to arrow functions names.forEach((s) => { console.log(s.toUpperCase()); });
Even though the parameter
s
didn’t have a type annotation, TypeScript used the types of theforEach
function, along with theinferred
type of the array, to determine the types
will have.This process is called
contextual typing
because thecontext
that the function occurred within informs what type it should have. -
Object types can also specify that some or all of their properties are optional. To do this, add a ? after the property name:
function printName(obj: { first: string; last?: string }) { // ... } // Both OK printName({ first: "Bob" }); printName({ first: "Alice", last: "Alisson" });
-
A union type is a type formed from two or more other types, representing values that may be any one of those types.
-
TypeScript will only allow an operation if it is valid for every member of the union. For example, if you have the union
string | number
, you can’t use methods that are only available on string:function printId(id: number | string) { console.log(id.toUpperCase()); // Property 'toUpperCase' does not exist on type 'string | number'. // Property 'toUpperCase' does not exist on type 'number'. }
-
Type aliases and interfaces are very similar, and in many cases you can choose between them freely. Almost all features of an interface are available in type, the key distinction is that a type cannot be re-opened to add new properties vs an interface which is always extendable.
// Extending an interface interface Animal { name: string; } interface Bear extends Animal { honey: boolean; } const bear = getBear(); bear.name; bear.honey; // Extending a type via intersections type Animal = { name: string; } type Bear = Animal & { honey: boolean; } const bear = getBear(); bear.name; bear.honey;
-
You can use a type assertion to specify a more specific type:
const myCanvas = document.getElementById("main_canvas") as HTMLCanvasElement;
-
You can also use the angle-bracket syntax (except if the code is in a .tsx file), which is equivalent:
const myCanvas = <HTMLCanvasElement>document.getElementById("main_canvas");
-
By themselves, literal types aren’t very valuable:
let x: "hello" = "hello"; // OK x = "hello"; // ... x = "howdy"; // Type '"howdy"' is not assignable to type '"hello"'.
It’s not much use to have a variable that can only have one value! But by combining literals into unions, you can express a much more useful concept - for example, functions that only accept a certain set of known values:
function printText(s: string, alignment: "left" | "right" | "center") { // ... } printText("Hello, world", "left"); printText("G'day, mate", "centre"); // Argument of type '"centre"' is not assignable to parameter of type '"left" | "right" | "center"'.
-
TypeScript also has a special syntax for removing null and undefined from a type without doing any explicit checking. Writing
!
after any expression is effectively a type assertion that the value isn’t null or undefined:function liveDangerously(x?: number | null) { // No error console.log(x!.toFixed()); }
- Arrays are object types in JavaScript.
-
But it turns out that in JavaScript,
typeof null
is actually"object"
! -
For methods, note that the parameter name is required. The function type
(string) => void
means a function with a parameter namedstring
of typeany
!