This content originally appeared on Telerik Blogs and was authored by Ifeoma Imoh
TypeScript is basically JavaScript with a type system, and in this post we’ll cover some of the most fundamental concepts of TypeScript.
JavaScript is undoubtedly one of the most popular programming languages of the decade, particularly among web and mobile application developers. Unfortunately, JavaScript was designed for prototypes and middle-sized applications, not large-scale and robust software applications. This was evident in its design system’s flaws and inadequacies, such as dynamic (loose) typing, implicit type coercion, etc.
TypeScript is a statically typed, open-source programming language introduced by Microsoft in October 2012. Fundamentally, it provides optional typing to JavaScript. The TypeScript programming language builds on top of JavaScript by adding optional static types, making it a typed superset of JavaScript.
TypeScript is JavaScript with a type system, and in this post we’ll cover some of the most fundamental concepts of TypeScript.
The ‘Why’ of TypeScript
- Static types: TypeScript improves the development experience (DX) by providing tools that enhance code uniformity and quality. TypeScript also provides documentation as it describes the inputs and outputs of a function and the structure of data in our application.
- Predictable code, easier to debug and scale: Though JavaScript is great for the flexibility it gives to its users, it can reach a point when it becomes messy, unreliable and buggy. To this end, TypeScript can help provide the necessary safety to organize code and catch bugs early enough before runtime.
- Using the latest features: JavaScript has different versions, some of which are yet to be fully supported by some browsers. TypeScript allows us to work with Javascript’s most recent, modern features without worrying about browser support. It can automatically close the gap between the different JavaScript versions during its compilation process.
- Others include IntelliSense (intelligent code completion), early bug detection, confidence when refactoring, etc.
Prerequisites
Before we can start developing apps with TypeScript, we need to have the following installed:
Node (npm)
Node is a JavaScript runtime environment. It ships with a tool called npm, which is short for Node Package Manager, responsible for maintaining a list of the packages (external libraries). Run this command to check if you already have npm installed:
npm -v
TypeScript Compiler
There are different ways of integrating TypeScript into your program. To keep things simple in this tutorial, we will install it using npm. Run this command in your terminal to check if you already have it installed:
tsc -v
If installed, you should see the version number printed; else, an error message is displayed.
If you don’t have it installed, run this command to install the TypeScript compiler globally on your computer:
npm install -g typescript
Upon successful installation, run this command to confirm the installation:
tsc -v
Code Editor/IDE
We will use the Visual Studio Code editor for this tutorial since it is very popular among developers, has a very useful integrated terminal and has built-in support for TypeScript. You can also use any other IDE of your choice.
Project Setup
First, we need to create a workspace containing a file where we can write our TypeScript program. To do that, run the following command in your terminal:
mkdir typescript-tutorial
cd typescript-tutorial
touch main.ts
Files ending with .ts
extension tell VS Code that it is a TypeScript file. Launch the VS Code app, then copy and paste the code below to the main.ts
file:
function greetings() {
console.log("Hello, TypeScript!");
}
greetings();
Compile a Basic TypeScript Program
To open the VS Code integrated terminal, press CTRL (or CMD) + ` on your keyboard. The integrated terminal should open at the bottom of the VS Code app, automatically pointing to your current working directory.
Now run this command to compile our file:
tsc main.ts
This command uses the TypeScript compiler we installed globally to compile our TypeScript code to a regular JavaScript code. You will notice that the compiler created a new file in the same directory as your TypeScript file named main.js
.
To execute this file, use the node command followed by the file name:
node main.js
You should see the message "Hello, TypeScript” printed on your console.
Type Annotation
TypeScript allows us to explicitly describe the type of an identifier (variables, function parameters and object properties). Type annotation in TypeScript is done using the “: type” syntax after an identifier, where “type” can be any valid type (string, number, boolean, etc.). Once an identifier is annotated as a string, only string values can be assigned to that identifier. The TypeScript compiler will throw an error if we attempt to assign a value other than a string to that identifier.
// After a variable name, we write a colon ":" and they a type.
let variableName: type = value;
const constantName: type = value;
The type of the identifier comes after your identifier (variable) name and is preceded by a colon.
Basic Types (Primitives)
Let’s take a look at how to add types to primitives.
let id: number = 26; // id is set to number
let name: string = "John Doe"; // name is of type string
let isLoggedIn: boolean = true; // isLoggedIn is set to boolean
In the above example, the variables id
, name
and isLoggedIn
are annotated as number, string and boolean. As a result, any attempt to assign a value not of the type mentioned above would result in a type
error. For example:
id = "twenty six"; // Error: Type 'string' is not assignable to type 'number'.
Essentially, TypeScript is giving us feedback directly in our code editor to let us know that we are attempting to assign a value of type string
to a variable annotated as number
, which isn’t allowed.
Reference Type
Objects
Objects in JavaScript can have as many properties as possible. In TypeScript, we type an object by type-annotating its individual properties.
let person: {
name: string,
age: number,
isLoggedIn: boolean,
};
person = {
name: "John Doe",
age: 24,
isLoggedIn: true,
};
As seen above, lines 1 to 5 define the type of the person
object. The type annotation ensures that we don’t accidentally supply fewer or more properties during the assignment and that each property receives the correct type of value.
Consider the following:
person = {
name: "John Doe",
age: 25,
};
// Error: Property 'isLoggedIn' is missing in type '{ name: string; age: number; }' but required in type '{ name: string; age: number; isLoggedIn: boolean; }'.
TypeScript is trying to tell us that isLoggedIn
is a required property as per annotation but is not included in the initialization.
Let’s look at another scenario with a different type of error.
person = {
name: "John Doe",
age: 25,
sex: "m",
isLoggedIn: boolean;
};
// Error: Type '{ name: string; age: number; sex: string; isEligible: boolean; }' is not assignable to type '{ name: string; age: number; isLoggedIn: boolean; }'.
// Object literal may only specify known properties, and 'sex' does not exist in type '{ name: string; age: number; isLoggedIn: boolean; }'.
This second scenario tries to add a property that was not specified on the person
type. TypeScript immediately flags an error saying that we are attempting to add a property, sex
, to the person
object.
Arrays
Arrays in JavaScript and other programming languages are a way of storing related elements in a single variable. We can annotate the type of a variable, function parameter and a return value to indicate that it is supposed to be an array.
// TypeScript syntax:
let names: type[] = [value1, value2, …];
// An array of strings
let employees: string[] = ["John", "Jane", "Jack", "Joe"];
// An array of numbers
let fibonacci: number[] = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34];
Any attempt to include a value that is not of the specified type immediately causes TypeScript to throw a type error.
employees = ["John", "Joe", "Jane", false]; // Type 'boolean' is not assignable to type 'string'.
Essentially, the error is saying, “Hey! A boolean value cannot be included in an array that only expects strings.”
TypeScript Tuple
TypeScript has another array variation that can hold a fixed length of values, ordered with specific types called Tuple. Tuples are fixed in their length and types and are exclusive to TypeScript; they don’t exist in JavaScript.
// Creating a Tuple with its type definition
let person: [string, number];
person = ["John Doe", 123];
The person
array declared above is referred to as a tuple with two elements. The first element is a string, and the second is a number.
Let’s look at some TypeScript errors that can occur if we don’t provide the correct value types.
person = [123, "John Doe"]; // TypeError type number is not assignable to type string. And 'type string is not assignable to type number.'
As we can see, it cares about not only the types in the length but also the order of those types.
Any attempt to add a third value to the person
tuple also flags an error.
person = ["John Doe", 123, false]; // type [string, number, boolean ] is not assignable to type [string, number].
What happens when we try to access a method or property not present in a certain type?
console.log(person[1].subString()); // property subString does not exist on type ‘number’
TypeScript is aware that person[1]
is of type number
, and the number
type has no property subString
.
TypeScript Enums
Enum is short for enumerated types, and it does not exist in JavaScript; it is unique to TypeScript. It allows us to define a set of named constants that can be numeric or string values.
Numeric Enums
Numeric enums are number-based. They store string values as numbers.
enum CardinalDirection {
North, // North = 0
East, // East = 1
West, // West = 2
South, // South = 3
}
TypeScript assigns numeric values starting at 0 to North, 1 to East and so on.
We can also manually assign values to our constants.
enum CardinalDirection {
North = 12,
East = 23,
West = 29,
South = 33,
}
console.log(CardinalDirection.North); // logs 12 to the console.
console.log(CardinalDirection[2]); // undefined
String Enums
String enums are similar to numeric enums except that they map string constants to string values. They are more readable and more often used than numeric enums. We can convert the numeric CardinalDirection
enum above into
a string enum like this:
enum CardinalDirection {
North = "north",
East = "east",
West = "west",
South = "south",
}
console.log(CardinalDirection.East); //logs east to the console.
Heterogeneous / Mixed Enums
Enums can also contain members or properties with mixed string and number values.
enum CardinalDirection {
North = 1,
East = "east",
West = 3,
South = "south",
} // Works fine but not advisable.
These kinds of enum are highly discouraged. Unless you have a strong reason, it is advised to refrain from using heterogeneous enums.
Also, any attempt to reference a constant that isn’t part of the enum will result in an error.
console.log(CardinalDirection.east); // Error
// property 'east' does not exist on type 'typeof CardinalDirection'. Did you mean 'East'?
TypeScript is smart enough to recognize that we have a typo and even help with suggestions.
The Union Type
A union type allows us to give a value multiple possible types using the single pipe (|
) character. When you compose a union type from primitives, the union is exclusive.
// This variable can be a number or a string
let id: string | number;
id = "1"; // ok.
id = 1; // ok as well.
id = [0, 1]; //Error: Type 'number[]' is not assignable to type 'string | number'.
However, the union over object type is inclusive, meaning the object can match more than one member type in the union.
type Person = {
name: string,
age: number,
};
type SchoolMember = {
major: string,
courses: string[],
};
type Student = Person | SchoolMember;
let student: Student = {
name: "Helen",
age: 24,
major: "Language Studies",
}; // This is ok. Typescript doesn't throw an error.
We can see from the above example that Student
can have a type of Person
or SchoolMember
, and TypeScript is OK with it.
Type Narrowing With Union Types
Since the union type allows us to give multiple types to a value, type narrowing enables us to perform a type check before working with the value.
const formatPrice = (price: string | number) => {
if (typeof price === "string") {
// in this block, TypeScript knows price is of type string, and IntelliSense suggests properties and methods applicable to string types.
} else {
// at this point, TypeScript knows price is of type number, and IntelliSense suggests properties and methods applicable to number types.
}
};
In the example above, we use typeof
to check what type price is before performing our operations, and TypeScript is smart enough to know what we are working with.
The Intersection Type
TypeScript allows us to combine multiple types using an ampersand (&
), which is particularly useful for object types.
type Person = {
name: string,
age: number,
};
type Lecturer = {
specialization: string,
yearOfExperience: number,
};
type Professor = Person & Lecturer;
let professor: Professor = {
name: "John Doe",
age: 42,
specialization: "Plant Biology",
yearOfExperience: 9,
};
In this example, we made a new type, Professor
, that is just a direct intersection of Person
and Lecturer
.
When you omit a property, say yearOfExperience
in type Lecturer
, TypeScript will throw a type error with the following message:
Property 'yearOfExperience' is missing in type '{ name: string; age: number; specialization: string; }' but required in type 'Lecturer'.
Special Types
In TypeScript, types like any
, never
, null
and undefined
do not refer to any specific kind of data. Hence they are called special types.
Any
The any
type is a way to opt in and out of type checking during the TypeScript compilation process allowing you to work with existing JavaScript codebases. A variable annotated to have the type any
can accept
values of all types. This type is not advisable as it tends to defeat the purpose of TypeScript.
let looseVariable: any;
looseVariable = 4;
looseVariable = "Hello, World";
looseVariable(); // doesn't throw an error
The type any
can also be used as an argument or a return type.
const looseFunction = (arg: any): void => {
// some logic goes here
};
looseFunction(55); // OK. No error.
looseFunction("I can also pass string here"); // This works too.
looseFunction({ one: "1", two: "2" }); // This works still.
The looseFunction
has its argument type set to type any
; hence any value of any type can be passed to it when it’s invoked.
Unknown
As mentioned above, the type any
can accept values of any type, and it is not type-checked at compile time.
In certain circumstances, this sacrifice may be too expensive for your application. If we want a variable to take any value while still being type-checked at compile time, then the unknown
type is our best bet. It is the type-safe
counterpart of the type any
.
let unknownValue: unknown;
unknownValue.toLowerCase(); // Error: Property 'toLowerCase' does not exist on type 'unknown'.
// If we set the type of unknownValue to 'any', Typescript wouldn't throw an error
Here TypeScript is saying the variable type is unknown; hence it can’t tell if the method toLowerCase
exists on the variable. To get around this, we need to use a type guard to narrow down the type.
let unknownValue: unknown;
if (typeof unknownValue === "string") {
// In this block, unknownValue is a string
unknownValue.toLowerCase();
} else if (typeof unknownValue === "number") {
//In this block, unknownValue is a number
unknownValue.toFixed(2);
} else {
// some other stuff can go here...
}
Void
The void
type is commonly used as the return type for functions that don’t return anything. When used as the type of a variable, the void
type almost always renders the variable useless as only undefined
can be assigned to it. Let’s see the void
type in action with the following examples.
let uselessVariable: void;
uselessVariable = undefined; // This works
uselessVariable = 0; // TypeError: Type 'number' is not assignable to type 'void'
uselessVariable = false; // TypeError: Type 'boolean' is not assignable to type 'void'.
uselessVariable = null; // This works only if strict mode is disabled.
uselessVariable = ""; //TypeError: Type 'string' is not assignable to type 'void'.
The void
type is used mostly when annotating the return types of functions to say the function shouldn’t return any value.
function sayHello(message) {
console.log(message);
}
In the example above, if you hover over the function’s name, you’ll see that TypeScript inferred the type for the return value to be void
even without it being annotated. This is because, as we can see, the function
doesn’t return anything, but we can also make it clear that it doesn’t return anything by annotating it.
function sayHello(message): void {
console.log(message);
}
Null & Undefined
In TypeScript, null
and undefined
are values of type null
and undefined
, respectively, but they can also be subtypes of other types. Like the void
type, they are less useful
because they are only assignable to types any
, unknown
, and themselves.
let n: null = null;
let u: undefined = undefined;
// Nothing else can be assigned to them.
n = undefined; // TypeError. undefined not assignable to null
u = null; // TypeError. null is not assignable to undefined
Never
The never
type denotes values that will never occur. It is only used when you are sure something will never occur. When used with variables, it signifies the variable will never be initialized or assigned a value.
let useLessVariable: never;
useLessVariable = null; // Error: Type 'null' is not assignable to type 'never'.
useLessVariable = undefined; // Error: Type 'undefined' is not assignable to type 'never'.
useLessVariable = false; // Error: Type 'boolean' is not assignable to type 'never'.
In the example above, TypeScript essentially says that the variable of type never
type can never be initialized, or no value can ever be assigned to it.
When used as the return type of a function, it means the function should never return. The function is either going to be one that always throws an exception, so there’s no chance for anything to be returned or a function that continuously runs in some sort of loop.
let useLessVariable: never;
function throwError(errMsg: string): never {
throw new Error(errMsg);
}
function infiniteLoop(): never {
while (true) {
console.log(
"I'll never get past this block, hence will never return a value"
);
}
}
If we try to return something from the function infiniteLoop
, TypeScript will complain because there’s not supposed to be anything ever returned from that function.
Type Inference
Until now, we have always explicitly specified the types of our variables. Another cool thing about TypeScript is that in some scenarios where the type of a variable is not stated, the TypeScript compiler can guess its type from certain values in your code smartly, and this is called type inference.
Let’s take some examples to see where TypeScript can infer the types of our variables:
1. When we declare and immediately initialize a variable
let counter = 1; // hover over the counter variable and Typescript would show something like "let counter:number"
counter = "some other stuff"; // TypeError: Type 'string' is not assignable to type 'number'.
Here, TypeScript can infer the type of the counter
variable based on the value assigned to it on line 1. Hence we could still get all the IntelliSense support and type-checking feature for type number
on our
counter
variable.
On the other hand, uninitialized variables would have the type any
.
let anyType; // Hover over this variable and see the type is set to any.
anyType = 4; // This works.
anyType = "Some string"; // This works too.
2. Setting default values for parameters
Like initializing a variable explained above, TypeScript can also infer the type of a parameter with a default value.
function addTwoNumbers(a = 7, b = 8) {
console.log(a + b);
}
addTwoNumbers("one", "two"); //Error. Argument of type 'string' is not assignable to parameter of type 'number'.
In the example above, even though we didn’t explicitly tell TypeScript the types of our parameters, it was able to figure it out from the default value assigned to them.
3. Determined return values
function addTwoNumbers(a: number, b: number) {
let c = a + b;
return c;
}
let result: number = addTwoNumbers(4, 6); // This works.
let result: string = addTwoNumbers(4, 6); //Error. Type 'number' is not assignable to type 'string'.
Because our function is just the sum of two number variables, TypeScript can infer that its return type is number
. This makes it evident since adding numbers yields a number.
Type Aliases & Type Assertion
Type Alias
In TypeScript, a type alias assigns a new name to a given type. It doesn’t necessarily create a new type—it just provides a reference for the type. It is often used for reference types as aliasing primitives aren’t
useful though it can be excellent for documentation. A type alias is created by preceding the name you want to give the type with the type
keyword.
// A type alias
type customType = string | number | null;
let myVariable1: customType = 4; // This is fine.
let myVariable2: customType = false; // Error: Type 'boolean' is not assignable to type 'customType'.
// A type alias
type statusCode = 200 | 201 | 400;
let response1: statusCode = 400; // This is fine.
let response2: statusCode = 202; // Error: Type '202' is not assignable to type 'statusCode'.
Type aliases can be reused in our code and can even be exported to be used in other files or modules.
type customType = {
name: string,
age: number,
married?: boolean,
};
A noticeable thing with our customType
above is that the married
property has a trailing question mark. Question marks are used to mark a property as optional. Hence any object annotated to be of type customType
may or may not supply the married
property.
// this type can be imported and reused in other files
export type customType = {
name: string,
age: number,
married?: boolean,
};
let person1: customType = {
name: "John Doe",
age: 33,
married: true,
}; // OK. All properties of customType are defined here.
let person2: customType = {
age: 33,
married: false,
}; // Error: Property 'name' is missing in type '{ age: number; married: false; }' but required in type 'customType'.
let person3: customType = {
name: "John Doe",
age: 33,
}; // OK. TypeScript isn’t sad because the married property was marked as optional.
Type Assertion
There are instances when you know more about a type than TypeScript does. One way to inform TypeScript of such type is using type assertion. Type assertion is a technique that tells TypeScript to treat a certain value as a certain type. It is purely effective at compile time and doesn’t affect the runtime behavior of our code.
Type assertion can be done in TypeScript in two ways: using the as
keyword and using the angle bracket (<>
) notation.
Using the as
keyword
Let’s assume we want to get a canvas
element in our document.
let canvas = document.querySelector("#canvas-element");
At this point, all TypeScript knows about the canvas
variable is some HTML element. It doesn’t know if it is an input
, a div
, a canvas
or a paragraph
element. But
when we write:
let canvas = document.querySelector("#canvas-element") as HTMLCanvasElement;
Here, TypeScript knows that the canvas
variable isn’t just an HTML element but a canvas
element.
Another example is:
let myVariable: unknown;
myVariable = "This is a string";
console.log(myVariable.length); //Error: Property 'length' does not exist on type 'unknown'.
console.log((myVariable as string).length); // prints 16 to the console.
Using the angle bracket <>
notation
The angle bracket notation version of type assertion works exactly like using the as
syntax, but when using TypeScript with JSX, only the as
keyword can be used for assertion.
So, let’s convert the example above to use the bracket notation:
let myVariable: unknown;
myVariable = "This is a string";
console.log(myVariable.length); //Error. Property 'length' does not exist on type 'unknown'.
console.log((<string>myVariable).length); // prints 16 to the console.
On line 4, we convert myVariable
from unknown
type to string
type, which is why we could access the length
property without any error from TypeScript.
Functions
Defining and using functions is one of the basic tasks in any programming language, and it’s no different with TypeScript. Functions are a way to group a set of related code into a logical block that can be reused.
TypeScript fully supports the existing syntax for functions in JavaScript and adds type information as its additional feature. The type information provides documentation and helps reduce the likelihood of bugs as we can’t pass or return incorrect data (types) from a type-safe function.
A typed function has the following syntax:
function functionName(optionalArg: type): returnType {
// some logic here
}
//ex. 1
function greet(): void {
console.log("hello, world");
} //accepts no argument and doesn’t return a value.
//ex. 2 creating a function with typed args and typed return value
function add(a: number, b: number): number {
return a + b;
} // accepts two arguement and returns the result of their addiction.
TypeScript ensures that the add
function receives exactly two arguments and must return a value of type number
. TypeScript would throw an error in any attempt to introduce a new argument or return a type other
than number
.
function add(a: number, b: number): number {
return a + b;
}
let result: string = adder(43, 59); // Error: Type 'number' is not assignable to type 'string'.
add(23, 43); // returns 66
add(23, 43, 14); // Error: Expected 2 arguments, but got 3.
add("one", "two"); //Error: Argument of type 'string' is not assignable to parameter of type 'number'.
It’s also important to add that this works with all types of functions (arrow functions, anonymous functions), not just named functions.
const add = (a: number, b: number): number => {
return a + b;
};
Optional Parameters
The parameters defined in a function can be marked as optional or given a default value, but not both. A question mark precedes the name of optional parameters. Also, optional parameters are defined first, followed by required parameters. A required parameter cannot follow an optional parameter.
function add(a?: number, b: number): number {
if (a) {
return a + b;
} else return b;
} // Error: A required parameter cannot follow an optional parameter.
// The correct version of the function would be:
function add(b: number, a?: number): number {
if (a) {
return a + b;
} else {
return b;
}
} // This works, and TypeScript is happy.
let result = add(5, 8); // result is 13.
let result2 = add(5); // result is 5.
Default Values
We can assign typed default values to function parameters to be used when a value is not passed as an argument to the parameter by the function caller.
function computeTax(income: number, rate = 0.1): number {
return income * rate;
} // rate will default to 0.1 if the caller doesn't specify
let netIncome1 = computeTax(4500); // rate is 0.1 hence computeTax returns 450
let netIncome2 = computeTax(45000, 0.2); // rate is 0.2 hence computeTax returns 225
Classes
Functionalities can be built into classes and objects created from those classes. JavaScript, until ES2015, had no such thing as classes. As with other features, TypeScript allows us to add types to the members of a class.
A simple class declaration looks like this:
class Student {
studentName: string;
studentId: number;
constructor(id: number, name: string) {
this.studentId = id;
this.studentName = name;
}
getStudentDetails(): string {
return `This is ${this.studentName} with the student ID of ${this.studentId}`;
}
}
The studentName
and studentId
are properties, while getStudentDetails
is a method of the class. The constructor method gets called when an instance of the class is created. The constructor
works very much the same as regular functions. They can have type annotations and default values. Let’s instantiate an object from the class defined above:
let myStudent = new Student(23, "Obi Madu");
The line of code above creates an object named myStudent
from the Student
class. The two arguments supplied are what gets passed to the id
and name
parameters defined on the constructor
function.
Let’s see what happens when we create an object with fewer or more arguments than the constructor expects.
let myStudent = new Student(); // Error: constructor Student(id: number, name: string): Student expected 2 arguments, but got 0.
let myStudent = new Student(23, "Obi Madu", "Economics"); // Error: Expected 2 arguments, but got 3.
let myStudent = new Student("Obi Madu", 23); // Error: Argument of type 'string' is not assignable to parameter of type 'number'.
There are instances where we want to prevent some class members from being accidentally modified after they’ve been initialized in the constructor function.
To do that with TypeScript, we can precede such members with the readonly
keyword. This is not a JavaScript keyword—it is a modifier we use in TypeScript to mark certain properties in classes, objects, etc. It is very
handy to prevent accidental modification.
class Student {
readonly name: string = "John Doe";
age: number;
constructor(name, age) {
this.name = name; // modification is allowed here
this.age = age;
}
randomMethod() {
this.name = "John Kenedy"; // Error: Cannot assign to 'name' because it is a read-only property.
}
}
const myStudent = new Student("Obi Madu", 25);
myStudent.name = "John Mark"; // Error: Cannot assign to 'name' because it is a read-only property.
TypeScript flags an error on lines 9 and 13, saying that you cannot assign to name
because it is a read-only property.
Interfaces
Interfaces are used to create reusable types that describe the shape of objects. They are very similar to type aliases, but a key difference between them is that interfaces can only be used with objects, not primitives or type literals. If an object implements an interface, it must define all variables and implement all methods declared on the interface.
// An interface
interface IStudent {
first: string;
last: string;
sayHello: () => string;
}
const student: IStudent = {
first: "Ifeoma",
last: "Imoh",
sayHello() {
return "Hello";
},
};
In the example above, notice that, unlike when defining a type alias, when defining an interface, there is no equals sign (=
) after the name of the interface. If any of the properties or methods defined in the IStudent
interface were not declared or implemented in the student
object, then TypeScript would flag an error except if the property was marked optional.
If you know beforehand the structure or shape of an object, you may want to declare an interface for that and make your object implement that interface. By doing so, you prevent any form of error or omission.
Generics
In TypeScript, generics allow us to define interfaces, functions and classes that are flexible and can work with multiple types, avoiding code duplication where necessary. For instance, we need to write a function that can either take a string, number or boolean argument and return it to the caller. Ordinarily, we would need to write three different functions, one for each.
function printString(arg: string): string {
return arg;
}
function printNumber(arg: number): number {
return arg;
}
function printBoolean(arg: boolean): boolean {
return arg;
}
Obvious code duplication. What if we were to define the same function for other types like tuples, objects and arrays? We’d keep duplicating the function by creating separate functions with different names. That’s where generics come to the rescue. Let’s rewrite the functions above using a generic type.
function printValue<T>(arg: T): T {
return arg;
}
This function accepts an argument, and it returns that argument. We customized the function to work with different types, so the input type of this function is what will be the return type as well.
You may have noticed the <T>
, known as the Type Parameter. Type parameters are placed inside an opening and closing angle bracket immediately following the function name. Although the type parameter
can be anything, the uppercase letter T
is conventionally used.
To call a generic function like our example above, we would write:
function printValue<T>(arg: T): T {
return arg;
}
printValue < string > "This is a string";
printValue < number > 342;
printValue < boolean > true;
Generic Classes
In the case of generic classes, the type parameter is placed immediately after the class name. A generic class may contain generic fields and generic methods.
For instance, we have a Student class with a name
field of type string
and an id
that can either be a string
or a number
.
class Student<T, U> {
private id: T;
protected name: U;
constructor(id: T, name: U) {
this.id = id;
this.name = name;
}
displayInfo() {
console.log("id is ", this.id, "and name is ", this.name);
}
}
let std1 = new Student<number, string>(101, "John Doe");
std1.displayInfo(); // prints "id is 101 and name is John Doe".
let std2 = new Student<string, string>("202", "Bruce Wayne");
std2.displayInfo(); // prints "id is 202 and name is Bruce Wayne".
From the example above, we can have more than one type parameter in our generics, and they only need to be comma separated in the angle bracket. Each type parameter is a placeholder for the actual type that would be passed when the class is instantiated.
Generic Interfaces
As with classes and functions, you can also use generics with interfaces. Interfaces, as discussed earlier, only help define the shape of an object. The compiler only uses them for type-checking, strips them off afterward, and they never make it to runtime. For example:
interface Person<T, U> {
name: T;
id: U;
}
let john: Person<string, string> = {
name: "John Doe",
id: "301", // OK. string passed to id here.
};
let bruce: Person<string, number> = {
name: "Bruce Wayne",
id: 301, // OK. number passed to id here.
};
Conclusion
While there is more to learn about TypeScript, this article exposes you to what you need to know to get started writing TypeScript. For more on TypeScript, you can check out the official documentation. To learn about using TypeScript with React, read this post.
This content originally appeared on Telerik Blogs and was authored by Ifeoma Imoh
Ifeoma Imoh | Sciencx (2022-12-13T14:46:00+00:00) Baby Steps With TypeScript. Retrieved from https://www.scien.cx/2022/12/13/baby-steps-with-typescript/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.