TypeScript: Getting Started
This is a small post to get to know how to use all of the most important features in TypeScript
Installation, Init, Configuration and Compile
Globally
npm install -g typescript
Develop
npm i typescript --save-dev
Init
npx tsc --init
will generate a file called tsconfig.json
where we are going to store all the TypeScript configs for our project, something like this:
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"watch": true, // This is not a default one, but it is very handy so TypeScript can regenerate files on the fly while coding
"sourceMap": true, // Not default option, but very handy for debugging
}
}
Compile
tsc myFile.ts
and it will generate a new file called myFile.js from a file called myFile.ts
, using tsc
command only will generate a js
file for each ts
file you have in the project if project config are not overwriting default ones.
CLI options To check more options that the TypeScript CLI has, you can check compiler options
Multiple tsconfig.json
In bigger projects, we may use multiple tsconfig files, to do so, you can name yout parent file to tsconfig.base.json
Later in your child folder, create a new one childFolder/tsconfig.json
and use the option extends
{
"extends": "../tsconfig.base", // this file is the parent one with default options
// These ones below are overwriting anything from parent
"compilerOptions": {
"removeComments": true
},
"include": [
"./**/*"
],
...
}
Built-in Types
Basic Types: Boolean, Number, String, Array, Enum
Aditional Types: Void, Null, Undefined, Never, Any
Type Annotations
let text : string = "I'm a text"; // declaring variable type string
text = 5; // this will give error, <Type 'number' is not assignable to type 'string'.>
// Example gettting value from a function
let price: number = GetPrice();
Type Inference
In this case, even if we don't annotate the variable as string
, typescript will know its type by inference, and it will give error trying to change variable value to number:
let anotherText = "I'm another string";
anotherText = 10; // this will give error
Union Types
It kinda like an or
for the type of variable
let someValue: number | string;
someValue = 5; // it works fine
someValue = "text"; // it works fine too
Using the --strictNullChecks Compiler Option
This option set to true, is going help us to avoid bugs and run time errors
let myText: string;
myText = null; // error
myText = undefined; // error
let nullableText: string | null | undefined;
nullableText = null; // ok
nullableText = undefined; // ok
Type Assertions
Sometimes, you know what type of variable you are going to recieve but compailer doesn't, you can use assertion like this:
let value: any = 5;
// Here we make the assertion that value is going to be number type
let fixedString: string = (<number>value).toFixed(2); //5.00
// You can use `as` too, to say value should be a number
let anotherString: string = (value as number).toFixed(2); //5.00
Non-null assertion operator
This way we tell TS that we know that variable is not null
const value = myNonNullObject!.property;
Writing Better Functions with TypeScript
Type annotations to functions
The ?
on message?
means message parameter is optional, since all function parameters are required by default in typescript.
The : string
after function(): string
means what kind of value are we going to return in that function.
function funFunc(score: number, message?: string): string {
return "I'm correctly type checked";
}
--noImplicitAny
By default is false, meaning any function paramether can be <any>
, but it's helpful to set it true, so, dev has to define the paramether type
// --noImplicitAny : true
function (s){ // it will give error, it needs to have a type diferent to any
return s + 2
}
Default-Initialized Parameters
// setting greeting parameter to "Good Morning" default value
// in case it is null
function sendGreeting(greeting: string = "Good Morning"): void {
console.log(greeting);
}
sendGreeting(); // Good Morning
sendGreeting("Good Afternoon!"); // Good Afternoon!
Arrow Functions
const myArrowFunction = (message: string): string => `new ${message}`;
Functions as Types
We can use functions already declared as types for our variables:
let logger: (value: string) => void; // here the type of the variable is a function
const logMessage = (message: string) => console.log(message);
function logError(err: string): void {
console.error(err);
}
const hasError: boolean = true;
if (hasError) {
logger = logError; // we assign variable to a function called logError
} else {
logger = logMessage; // we assign variable to a function called logMessage
}
logger("My String"); // My String
Creating and using custom types
Interfaces vs Clasess
Define a new type | Define a new type Properties (signatures) | Properties (with implementation) Methods (signatures) | Methods (with implementation) Cannot be instantiated | Can be instantiated
Note: Interface are not compiled to JS code, they are used in TSC development code, clases are compiled though
Interfaces
Creating an Interface
An interface define the shape of an object.
// simple interface
interface Employee {
name: string;
title: string;
}
// Extending an interface
interface Manager extends Employee {
department: string;
numOfEmployees: number;
scheduleMeeting: (topic: string) => void;
}
Using an Interface
You can use an interface in an object for example as long as that object has all the properties required by interface, if it has more properties that aren't in the interface you can still use it:
interface Employee {
name: string;
title: string;
age?: number; // optional
}
const developer = {
name: "Jonny Acevedo",
title: "Senior Front End Developer",
editor: "VSC", // This one is not defined in the interface, but it still works ok
};
let newEmployee: Employee = developer; // ok
Note: Adding a question mark ?
after interface property name, it marks it as optional
Class Members
- Method implementations
- Property implementations
- Accessors (getters and setters)
- Access modifiers
- Public (Class members are public by default)
- Private
- Protected
class Developer {
department: string;
private _title: string;
get title(): string {
return this._title;
}
set title(newTitle: string) {
this._title = newTitle.toUpperCase();
}
documentRequirements(requirements: string): void {
console.log(requirements);
}
}
TSC private vs ECMAScript private fields
private
is a technique for restricting access to that member at design time, TSC will stop attempts to access this member from outside the class, but once compailed to JS, that property would be accessible
To make it truly private when compailed to JS, from TypeScript 3.8 you can add #
to the class property and it will make it private in the compailed JS as well
class Developer {
private _title: string;
#salary: number;
}
Extending a class
class Developer {
department: string;
private _title: string;
}
// Extending will inherit Developer properties and add new ones
class WebDeveloper extends Developer {
favoriteEditor: string;
customFunction(): void {
// void
}
}
let webDev: WebDeveloper = new WebDeveloper();
webDev.department = "Software Engineering";
webDev.favoriteEditor = "VSC";
Implementing an Interface
interface Employee {
name: string;
title: string;
logId: () => string;
}
// use [implements] keyword
class Engineer implements Employee {
name: string;
title: string;
logId() {
return `${this.name}_${this.title}`;
}
}
Configuring a project with multiple source files
If we want to ship multiple TSC files in one single JS file, we could use this technique:
tsconfig.json
{
...
"compilerOptions":{
...
"outFile": "../js/app.js"
},
"files": [
"./app.ts"
],
...
}
Later, in
app.ts
/// <reference path="employee.ts" /> // This is going to reference other TS file
class WevDev implements Employee{
...
}
Static Members
Static members are a nice way to add utility or helper methods that are related to the purpose of the class, but aren't dependent on any data that might be stored in instances of the class.
class WebDeveloper extends Developer{
static jobDescription: string = "Build cool things";
static logFavoriteProtocol() {
console.log("HTTPS, of course");
}
logJobDescription(): void {
// we don't use `this` keyword, we can use class name
console.log(WebDeveloper.jobDescription);
}
}
// we don't need to create a new class instance with the `new` keyword
// we can just use class name dot property
WebDeveloper.logFavoriteProtocol();
Constructors
Special type of function executed when new instances of the class are created, handy for perform any special initialization or setup.
class Developer {
constructor() {
console.log("Creating a new dev");
}
}
class WebDeveloper extends Developer {
//[readonly] value can only be set when variable is declared
// or inside a constructor
readonly favoriteEditor: string;
constructor(editor: string) {
//[super()] calls the parent class constructor first // Creating a new dev
super();
this.favoriteEditor = editor;
}
}
Initializing class properties with constructor
Long Way:
class Developer {
name: string;
age: number;
constructor(name: string, age: number) {
this.name = name;
this.age = age;
}
}
Short Way
class Developer {
constructor(public name: string, public age: number) {}
}
Creating and consuming Modules
- Encapsulation
- Reusability
- Create higher-level abstractions
Exporting a Declaration
Just like in JS ES6
export interface person {}
export function hireDeveloper(): void {}
// using default
export default class Employee {}
Export Statements
interface Person {}
function hireDeveloper(): void {}
class Employee {}
export { Person, hireDeveloper, Employee as StaffMember };
Importing from a Module
import { Person, hireDeveloper } from "./person";
let human: Person
// this is an example of importing a [default]
// you can set the alias to any name you want
import Worker from "./person";
// Here we import [StaffMember] class with the alias [CoWorker]
import { StaffMember as CoWorker } from "./person";
// Import an entire module
import * as HR from "./person";
// File in root
import { myFunction } from "/fileInRoot";
// non-relative imports
import * as lodash from "lodash";
Type Declaration Files
- TypeScript wrapper for JavaScript libraries
- Types for variables, functions, etc
- Define valid property names
- Define function parameters and much more
- Types for variables, functions, etc
- Development-time tool
- Filenames end with
.d.ts
- Available for thousands of libraries
DefinitelyTyped
https://github.com/DefinitelyTyped/DefinitelyTyped
- Github repository containing thousands of type declaration files
- Declaration files often maintained independent of related javascript library
- Source for installation utilities
Installation
One can go to: https://www.typescriptlang.org/dt/search?search=lodash and search for the library desired and you will find instructions to easily install library and its TS types with npm/yarn, in this example, lodash will be like:
npm i lodash
npm i @types/lodash --save-dev
// in your [file.ts], you import it as usual
import * as _ from "lodash"
Thanks for reading, these were my notes for TypeScript, getting started.