TS is a syntactic super-set of JS which adds static typing
TLDR: JS has a lot of BS, TS adds type safety.
TypeScript uses compile time type checking. Which means it checks if the specified types match before running the code, not while running the code.
A common way to use TS is to use the official TS Compiler, which transpiled TS --> JS.
npmitypescript--save-dev
You can run the compiler with:
npxtsc
You can configure the TS compiler using a tsconfig.json file. It can be generated with:
npxtsc--init
The above is just one option to get started with TS; you can also use create-react-app then check off TS, etc...
npxcreate-react-app<app_name>--templatetypescript
npxcreatevite@latest<app_name>--templatereact-ts
npmcreate-next-app@latest--ts
Simple (Primitives) Types
There are three main primitives in JavaScript and TypeScript.
boolean - true or false values
number - whole numbers and floating point values
string - text values
Type Assignment
When creating a variable, there are 2 main ways to assign a type:
Explicit - writing out the type:
let firstname:string="Dev";
easier to read and more intentional
Implicit - TS will infer the type based on the assigned value:
let firstname ="Dylan"
shorter, faster, and more often used when developing and testing
Type Assignment Error
TS will throw an error if data types don't match
// EXPLICITlet firstName:string="Dev"; // stringfirstName =19; // throws error: value is number// IMPLICITlet firstname ="Dev";firstname =19// still throws error
Unable to Infer
TS may not always properly infer the variable type.
In such cases, it will set the type to any, which disables type checking
Special Types
TS has special types that may not refer to any specific type of data.
> Type: any
type that disables type checking and allows all types to be used.
let x:any=true;x ="string"// no error as it can be 'any' typeMath.round(x) // same as above
> Type: unknown
similar, but safer alternative to any.
TS will prevent unknown types from being used
let w:unknown=1;w ="string"; // no errorw = {runANonExistentMethod: () => {console.log("I think therefore I am"); }} as { runANonExistentMethod: () =>void}// How can we avoid the error for the code below when we don't know the type?w.runANonExistentMethod(); // Error: Object is of type 'unknown'.
best used when you don't know the type of data being typed.
To add a type later, you'll need to cast it.
Casting is when we use the "as" keyword to say property or variable is of the casted type
> Type: never
never effectively throws an error whenever it is defined.
// Error: Type 'boolean' is not assignable to type 'never'. let x:never=true;
> Type: undefined & null
undefined and null are types that refer to the JS primitives undefined and null, respectively.
let y:undefined=undefined;let z:null=null;
These types don't have much use unless strictNullChecks is enabled in the tsconfig.json file.
Arrays
For Explicit assignment, add a [ ] after the value type (number[ ], ...)
constnames:string[] = [];names.push("Dev") // works just like JS array// TS will throw type error for incompatable valuesnames.push(3) // can't push number to string[]
Readonly
The readonly keyword can prevent arrays from being changed.
constnumbers= [1,2,3] // inferred to type: number[]numbers.push("4") // error// when reading values:let firstNum:number= numbers[0];
Tuples
array w/ a pre-defined length and types for each index and allow for arrays with different types, all of which are known.
Defining Tuple
To define a tuple, specify the type of each element in the array:
let myTup: [number,boolean,string]; // define typesmyTup = [99,true,"HOV"] // initialize must correspondmyTup = [true,"HOV",5] // ERROR; ORDER MATTERS
Readonly Tuples
It is suggested to make tuples readonly since there's no type safety in the tuple outside the first n indexes with strongly defined types:
// We have no type safety in our tuple for indexes 3+let ourTuple: [number,boolean,string];ourTuple.push('4th index is not type safe');// Instead, use readonlylet ourTuple:readonly [number,boolean,string] = [99,true,'LP']ourTuple.push('4th index is not type safe'); // ERROR
Named Tuples
Named tuples allow us to provide context for values at each index:
constgraph: [x:number, y:number] = [55.2,41.3];
Destructuring Tuples
Since tuples = arrays, we can also destructor them:
TS has specific syntax for Objects (python dictionaries). It basically adds a schema for each Object, allowing for inferences and type safety.
constcar: {// SCHEMA type:string, model:string, year:number} = {// VALUES type:"Acura", model:"NSX", year:2017}; // TS infers types of properties, and will throw errors:car.year =2020// no errcar.year ="2020"// err (year: number)
Optional Properties
properties followed by a ? don't have to be defined during Object definition.
// NO ERROR; 'mileage' is optionalconstcar: { type:string, mileage?:number } = { type:"Toyota",};
Index Signatures
When you don’t know all the names of a type’s properties ahead of time, but you do know the shape of the values, you can use an index signature to describe the types of possible values:
/*StringArray interface has the index signature, which states that when a StringArray is indexed with a number, it will return a string.An index signature property type must be string/number.*/interfaceStringArray { [index:number]:string;}constmyArray:StringArray=getStringArray();constsecondItem= myArray[1];
Enums
enum is a special class that represents a group of 'const' variables. They come in 2 flavours: numeric and string.
1. Numeric Enums
Default Numeric Enums: where enums will initialize the first value to 0 and add 1 to each additional value:
TS allows types to be defined separately from the variables that use them. You can use Aliases & Interfaces to be easily shared b/n different variables/objects.
Type Aliases
doesn’t actually create a new type - it creates a new name to refer to that type.
// ex. Aliasing on Primitives (not really useful)typeSecond=number;let time:Second=10; // same as::: let time: number = 10// ex. Aliasing on Complex types like Objects (helpful)typeCarYear=number;typeCarType=string;typeisCarInsured=boolean;typeCar= { year:CarYear; // number type:CarType; // string isInsured:isCarInsured; // boolean}// now you can use this 'Car' type for different Car objectsconstmyCarYear:CarYear=2001;constmyCarType:CarType="Sportscar";constisMyCarInsured:isCarInsured=true;constmyCar:Car= { year: myCarYear, type: myCarType, isInsured: isMyCarInsured}console.log(myCar.year); // 2001console.log(myCar.type); // Sportscarconsole.log(myCar.isInsured); // true
Interfaces
similar to type aliases, except they only apply to OBJECTS
using the | like: | , we are saying that _ is type1 or type2
// @param 'code' can be either string OR number (either works)functionprintStatusCode(code:string|number) {console.log(`My status code is ${code}.`)}printStatusCode(404); // WORKSprintStatusCode('404'); // WORKS// make sure that the code works fine for both type1 and type2; ie. don't call string functions on _ if _ may also be a number.
Functions
TS has specific syntax for adding types to fx parameters & return values:
// Note: TS will infer types if they're not explicit// syntaxfunction <myFunc>(<param>: <type>): <returnType> {return...}{/* Void Return Type*/}constsayHi= (name:string):void=> {console.log(name) }{/*Optional? + Default= Parameters*/}functionaddNums(a:number, b?:number=1):number {return a + b;}{/*Rest Parameters (type must be array)*/}functionadd(a:number, b:number,...rest:number[]) {return a + b +rest.reduce((p, c) => p + c,0);}
Export & Import Functions
Use named exports (unlimited exports in a file):
exportfunctionsayHi():void {...}// to import:import {sayHi} from'./path2file'
Use default exports (1 per file):
exportdefaultfunctionsayHi():void {...}// to import:import sayHi from'./path2abovefile'
If you have a file that does 1 default export & many named exports:
// 👇️ default exportexportdefaultfunctionsum(a:number, b:number):number {return a + b;};// 👇️ named exportexportconstexample='hello world';// in another file, to import:import sum, { example } from'./path2abovefile'
Function Overloading
Overloading - same name, different parameters and return types
allows you to convert a variable from 1 type to another.
Casting w/ as
let x:unknown='hello';console.log((x asstring).length);// CASTING DOESN'T CHANGE THE TYPE OF THE DATA IN THE VARIABLElet x:unknown=4;console.log((x asstring).length); // err (x still holds number)// TO FORCE CAST, CAST TO 'UNKNOWN'let x ='hello';console.log(((x asunknown) asstring).length); // x is not actually a number so this will return undefined
Casting w/ *<>*
// WONT WORK IN .TSX (REACT) !!!let x:unknown='hello';console.log( (<string>x).length );
Basic Generics
Suppose you have an array; 'A' list of items. You don't know what the items are nor do you care. This means that you have an array of generic A elements. What A is is IRRELEVANT.
Many DS&A do not depend on the actual type of the object. However, you still want to enforce a constraint between various variables. Ie. a list reversal algorithm:
/*Here you are basically saying that reverse() takes an array (items: T[]) of some type T (notice the type parameter in reverse<T>) and returns an array of type T (notice : T[]). */functionreverse<T>(items:T[]):T[] {let toreturn = [];for (let i =items.length-1; i >=0; i--) {toreturn.push(items[i]); }return toreturn;}// ie. passing number[] to reverse() will result in return type number[] var sample = [1,2,3];var reversed =reverse(sample);console.log(reversed); // [ 3, 2, 1 ]// ie. passing string[] to reverse() will result in return type string[] var strArr = ['1','2','3'];var reversedStrs =reverse(strArr);console.log(reversedStrs); // [ '3', '2', '1' ]
Type Aliases (and also for Interface)
Generics in type aliases allow creating types that are more reusable.
Generics can be assigned default values which apply if no other value is specified or inferred.
// default value for Generic = string (type for _value variable which is set by the user)classNamedValue<T=string> {private _value:T|undefined;constructor(private name:string) {}publicsetValue(value:T) {this._value = value; }publicgetValue():T|undefined {returnthis._value; }publictoString():string {return`${this.name}: ${this._value}`; }}let value =newNamedValue('myNumber');value.setValue('myValue');console.log(value.toString()); // myNumber: myValue
Extends
Constraints can be added to generics to limit what's allowed
// v1 must be type S=String/Number// v2 must be type T=String/NumberfunctioncreatePair<Sextendsstring|number,Textendsstring|number>(v1:S, v2:T): [S,T] {console.log(`creating pair: v1='${v1}', v2='${v2}'`);return [v1, v2];}
Utility Types
TS comes with a large number of types that can help with some common type manipulation, usually referred to as utility types.
Partial
all the properties in an object are now optional.
interfacePoint { x:number; y:number;}let pointPart:Partial<Point> = {}; // x and y are optionalpointPart.x =10;console.log(pointPart); // { x: 10 }
Required
all the properties in an object are now required.
interfaceCar { make:string; model:string; mileage?:number; // mileage is optional here, but Required<Car> makes it required}let myCar:Required<Car> = { make:'Ford', model:'Focus', mileage:12000// `Required` forces mileage to be defined};
Record
shortcut to defining an object type with a specific key type and value type.
interfacePerson { name:string; age:number; location?:string;}constbob:Omit<Person,'age'|'location'> = { name:'Bob'// Omit<Person> has removed age & location from Person type thus they can't be defined here};
Pick
removes all but the specified keys from an object
interfacePerson { name:string; age:number; location?:string;}constbob:Pick<Person,'name'> = { name:'Bob'// Pick<Person, 'name'> has only kept name, so age & location were removed from Person type thus they can't be defined here};