- files here
- Introduction & Setup
- Compiling TypeScript
- Type Basics
- Objects & Arrays
- Explicit Types
- Dynamic (Any) Types
- Better Workflow &
tsconfig
- Function Basics
- Type Aliases
- Function Types (Signatures)
- The DOM & Type Casting
- Classes
- Public, Private, Readonly
- Modules
- Interfaces
- Interfaces and Classes
- Rendering an HTML Template
- Generics
- Enums
- Tuples
- superset of JavaScript with strict type system amongst other things
- error checking and debugging becomes easier
- because browsers don't understand TS by default, it must be compiled to JS
npm init npm install --save-dev typescript npm install --save-dev commonjs
- Additional features like generics, interfaces, tuples, and more
- Using Microsoft's Live Preview to have a server serving this content in the browser at port 3000
- TypeScript code must be compiled before if can be rendered in the browser
- This can be done with the following command:
npx tsc app.ts app.js
where app.ts
could be replaced with any input TS filename, and app.js
could be replaced with any output JS filename
- can't have both files open at the same due to naming conflicts
- if you want the input and output files to have the same name you can leave off the output file name
- if you want typescript to watch a file and automatically recompile TS code to JS code on saved changes:
npx tsc app.ts -w
- the type a variable is at declaration is the type it must stay as
- if you try to assign a different type to it later, you will get an error
- you cna however change it's value withing the declaration type
- TS infer types based on assignment, but they can also be explicitly defined
- You should however define the type for function parameters to ensure type checking
- Type checking is done at compilation > i.e. code won't compile to JS if type errors exist
- All elements of an array must be of the same type if the original array is declared with just a single type
- You can have an array of mixed types only if it is declared with mixed types
- you can also redefine an index of the array as a different type i.e. if
arr[0]
was defined as a number, it can reassigned to any type as long as the array was declared with mixed types
- you can also redefine an index of the array as a different type i.e. if
- In an object, the key-value pair assignents work like variable assignments in that the type of a particular key cannot be changes
- Once an object is declared, you cannot add or remove a property
- Additionally, arrays must remain arrays, and likewise for objects
- To initialize a variable without initializing it, we should give it an explicit type since there is nothing for TS to infer from.
let age: number;
- The same type strictness will now apply
- A similar thing can be done with arrays
let ninjas: string[] = [];
// assigning to an empty array so that it is alread initialized for use
- We use the
union
type to define multiple the types a variable may house
let ninjas: (string | number)[] = [];
// could add a string or number, but not a boolean
let uuid: string | number;
- We can similarly define a variable as an object, which is a superclass of array
let obj: object;
// could be an object or array
let obj2: {
name: string;
age: number;
};
// more structure to be enforced when the variable is given a value
any
type for variables that can be of any type, and can therefore also change type over time
let age: any;
age = 25;
age = "twenty-five";
// both are acceptable
let anyArr: any[] = [];
// can add any values to this array
let obj: {
name: any;
age: any;
};
// same for an object; still can't add or remove properties
- kind of reverts TS back to JS, removing a lot of its useful features
- May want to structure you project separating your
public
files (i.e. CSS, HTML, compiled JS code), from yoursrc
source TS files - But how do we connect these files? > with
tsconfig
npx tsc --init
- This creates a
tsconfig.json
file:- could change
"target"
toESNext
fromes5
, indicating the higest JS version your TS version supports - could also change
"module"
toESNext
- set
"outDir"
to where you want compiled JS files, and"rootDir"
to where your source TS files will be housed
- could change
- Must also tell TS what to watch now with
npx tsc -w
- unfortunately, this now has TS watching all file with the extension
.ts
, and compiling them in thepublic
folder, even those outside of the sourcesrc
folder - to address this, you can specify which files should be included in watching in the
tsconfig.json
file by adding the following at the top level of the object:
{ ..., "include": ["src"] }
- unfortunately, this now has TS watching all file with the extension
- TS will infer the type of a function variable as well when it is defined
- Could also define this explicitly
let greet: Function;
// can be a traditional or arrow function
- Recall, you can also explicitly type function parameters
- You can also define functions with optional types, and make parameters themselves optional as well, and default values
const add = (a: number, b: number, c: number | string) => {};
// optional type
const add = (a: number, b: number, c?: number | string) => {};
// optional variable; defaults to undefined
const add = (a: number, b: number = 10, c?: number | string) => {};
// default value; don't need to make a variable optional if it is given a default
// leave optional and defalut params until after other params
- The return type of a function may be inferred, but best to be explicit
const add = (a: number, b: number = 10): void => {
console.log("adding...");
};
// void return type
const add = (a: number, b: number = 10): number => {
return a + b;
};
// numeric return type
- If you assign the return value of a function to a variable, note that this will set the infered type of that variable, and it cannot be changed
let result = add(1, 3);
// the type of result is now inferred to be the return type of add() which is a number
- Type aliases can be defined to reuse type specifications for DRYer code
type StringOrNum = string | number;
// type alias
const logDetails = (uuid: StringOrNum) => {};
const logMore = (uuid: StringOrNum, hash: number) => {};
- Function signature decribe in more detail a function, including the arguments (nd their types) it takes, and the return value(s) and type(s)
- names of params in signature need not match the final name of the params in the defined function
let logDetails: (obj: { name: string; age: number }) => void;
type person = { name: string; age: number };
logDetails = (ninja: person): void => {
console.log(`${ninja.name} is ${ninja.age} years old`);
};
- TypeScript doesn't have special access to your HTML, so it doesn't know if certain element will exist (or be null) after compilation
- As a result, you must either handle for null values (conditional statements), or, explicitly let TS know that a particular HTML element exists
- this is done by putting an exclaimation point at the end of assignment before the semi-colon
- TS also has special types for each DOM element type
- knows all properties and methods available on that element type
- for example, it knows that
<a>
tags (of typeHTMLAnchorElement
) have ahref
property
- TS however cannot infer the type of an element if it is grabbed by something other then it's tag type i.e. if you grab it by its class
- in this case, you must type cast that element if you want access to its specific type properites and methods
- Classes in TS behave much like how they do in JS, with the addition of the strict type system
- To make an array of a user-defined class, you can do the following:
class MyClass{
...
}
let myClassArr: myClass[] = [];
- TypeScript provides access modifiers (like in Java) for class properties and methods
public
: any access and modification inside and outside of the classprivate
: only access and modification within a classreadonly
: can access from inside and outside the class, but not modify it in either context
- accessors can be applied whenever a variable is first declared, either at the beginning of a class or within a class constructor
- We may wish to split our code out into separate files via ES6 modules i.e. what you
import
andexport
- because
modules
are only supported for newer browsers, TS doesn't compile them down to something older browsers will understand, so it's important to be using a modern browser to enable this functionality - Another option is
webpack
+ TS (out of scope) for bundling and support for older browsers
- because
- To use modules, in any
<script></script>
tags that have a compiled JS module (i.e. one that was written in TS then compiled to JS code in youas asrc
, set the propertytype="module"
- for the difference between type aliases and intefaces (they look quite similar...) read this
- recall, interfaces are like contracts, telling you what you can expect from a class that implements or other interface that extends it
- an instantiation of an interface must have all properties and only the properties defined in the interface
- this can be very helpful in enforcing what argements are passed as parameters to a function
// interfaces
interface IsPerson {
name: string;
age: number;
speak(a: string): void;
spend(a: number): number;
}
const me: IsPerson = {
name: "reed",
age: 25,
speak(text: string): void {
console.log(text);
},
spend(amount: number): number {
console.log(` spent ${amount}`);
return amount;
},
};
- classes can implement interfaces
- intefaces can be used much like types in that they can be used to enforce the structure and content of variable, lists, and classes
- make use of the
render()
function defined in a class directly - interacting with the DOM directly using the
document
JS interface
- Generics are a feature in TS that allow us to create blocks of reusable code that can be used with different types
const addUID = (obj: object) => {
let uid = Math.floor(Math.random() * 100);
return { ...obj, uid };
};
let docOne = addUID({ name: "yoshi", age: 40 });
console.log(docOne);
console.log(docOne.name); // error
// but can't access any properties of the object because the function doesn't know what kind of object this is
// and therefore, we can't know the properties of the returned object
// must use generics
- we can capture the specifics of whatever type is passed in like this
const addUID = <T>(obj: <T>) => {
let uid = Math.floor(Math.random() * 100);
return { ...obj, uid };
};
// but we're no longer enforcing that this parameter must be an objects
- instead, we can enforce that whatever is returned must extend the type that is passed in
const addUID = <T extends object>(obj: <T>) => {
let uid = Math.floor(Math.random() * 100);
return { ...obj, uid };
};
- or even more precisely
const addUID = <T extends {name: string}>(obj: <T>) => {
let uid = Math.floor(Math.random() * 100);
return { ...obj, uid };
};
- alternatively, generics can be implemented with interfaces
interface Resource<T> {
uid: number;
resourceName: string;
data: T;
}
const docThree: Resource<object> = {
uid: 1,
resourceName: "person",
data: { name: "reed" },
};
const docFour: Resource<string[]> = {
uid: 1,
resourceName: "person",
data: ["reed", "mcdaniel"],
};
- enums are a special data type that allow us to store a collection of constants and assiciate each one with a numeric value
enum ResourceType {
BOOK,
AUTHOR,
FILM,
DIRECTOR,
PERSON,
}
interface Resource<T> {
uid: number;
resourceType: ResourceType;
data: T;
}
const docThree: Resource<object> = {
uid: 1,
resourceType: ResourceType.BOOK,
data: { name: "reed" },
};
console.log(docThree.resourceType); // returns 0 i.e. the index of BOOK in the enum
- tuples, like in python, are immutable in that the type at a particular index cannot be changed
// lists
let arr = ["ryu", 25, true];
arrr[0] = false;
arr = [30, false, "yoshi"];
// the above is all fine for lists
//tuple
let tup: [string, number, boolean] = ["ryu", 25, true];
tup[0] = false; // error
tup[0] = "yoshi"; // allowed
- tuples are useful for eforcing structure on a collection, for example passing in args as a tuple
let student: [string, number];
student = [98, "reed"]; // error
student = ["reed", 98]; //allowed