A system where we make sure that every value in the code, has a type associated to it.
Typeis an easy way to refer to the differentproperties&functions/methods, that avaluehas.value: In JS & TS, avalueis any data that we assign to a variable, and so, to a variable, we can assign the following kinds ofvalues:strings,numbers,booleans,nulls,undefined,objects,functions,classes,arrays, and so on... All of these different values, have types, i.e., Anobjecthas a type, afunctionhas a type, anarrayhas a type, and so on...- So when we refer to the
typeof avalue, we're trying to also refer to the different kind ofpropertiesandfunctionsthat that particularvaluewith that particulartypehas.
Example:"red"is astringtypevalue. By which, we can also infer that"red"is avaluethat has all thepropertiesandmethodsthat we assume that astringtype has.
-
Basic Examples of Types:
Type Values That Have This Type string 'hi there',"",'Today is Monday'number .000025,-20,40000000boolean true,falseDate new Date()Todo { id: 1, title: "Something", completed: true }As we can see from the table above, every
valuein typescript, has a type, i.e., even when we make aDate()object, that in itself has a type of Date. And for the the object with thevalueas the following{ id: 1, title: "Something", completed: true }also can have a type called Todo, if that's theinterfacefor the object that we made in the codebase (viz.interface Todo { id: number; title: string; completed: boolean; }) -
How do these types relate to each other?
- In the world of TS, we've two different categories of types:
- Primitive Types: These are the basic types in TS, which are
number,boolean,void,undefined,string,symbolandnull. - Object Types: These are types that the user can create, or some of the object types are built into the language itself, and these object types are (not limited to)
functions,arrays,classes andobjects.
- Primitive Types: These are the basic types in TS, which are
- Why do we care about types at all?
We care about types primarily because of the following 2 reasons:- Types are used by the Typescript Compiler to analyze our code for errors. Eg: If a value that we access has a type already defined in TS, and if we try to access some property/method that doesn't exist for that type, then TS will throw an error saying "Property/Method doesn't exist on type <type of the
value>". - Types allow other engineers to understand what values are flowing around our codebase. Eg: When working in a large codebase, sometimes the programmers might use very poorly written argument names such as
a,t,d, etc, and to know the type associated to these kind of argument names is really hard. And so, if we know the types related to these kind of argument names, it would make the life of other engineers easier to go through the codebase and understand what the code does.
- Types are used by the Typescript Compiler to analyze our code for errors. Eg: If a value that we access has a type already defined in TS, and if we try to access some property/method that doesn't exist for that type, then TS will throw an error saying "Property/Method doesn't exist on type <type of the
- In the world of TS, we've two different categories of types:
We kind of use these types everywhere. It always better to have a system which is type-safe. If we find the errors during the development stage itself and fix them then and there, the testing of the applications becomes much easier and so, there will be less number of bugs/errors popping up when the app is being used in production.
Type Annotation & Type Inference are actually 2 different features inside of TS. But these 2 different features work in parallel and apply slightly differently to Variables, Functions & Objects.
- Type Annotations: Code we add to tell TS what type of value a variable will refer to. Basically, we (the developers & programmers) tell Typescript what the type of a certain variable/function/object is.
- Type Inference: TS tries to figure out what type of value a variable refers to. Here, Typescript guesses the type of the variable/function/object for which, the types have already been annotated.
Whenever we make a variable/const, we can do so in 2 ways:
- Declare and initialize the variable in a single line.
Example:const color = "red";// here, the type ofcolorwill be inferred by TS to be a'string' - Declare the variable, and initialize it later.
Example:let color;color = "red";// here, the type ofcolorwill be inferred by TS to beanytype
Therefore, whenever we declare and initialize on the same line, TS will figure out the type for us. But whenever we declare in one line, and initialize in another line, TS will infer the type of the variable to be any.
In general, we're going to rely on Type Inference, always! i.e., whenever we can, we'll rely on Type Inference (viz. declaring and initializing the variable without annotating the type).
But there are scenarios where we rely on Type Annotation, and these scenarios are:
- Whenever we declare a variable on one line then initialize it later.
Example:- Instead of this:
let color;// no annotation given => type ofcolorisanycolor = "red"; - We will do this:
let color: string;// type is annotated => type ofcoloris'string'color = "red";
- Instead of this:
- When a function returns the
anytype and we need to clarify the value.
Example: A good example for this problem is the usage of theJSON.parse()method.- Instead of this:
const json = '{"x": 10, "y": 20}';//jsonis exactly of type{"x": 10, "y": 20}const coordinates = JSON.parse(json);//coordinatesis of typeanyconsole.log(coordinates);// { x: 10, y: 20 } - We will do this:
const json = '{"x": 10, "y": 20}';//jsonis exactly of type{"x": 10, "y": 20}const coordinates: { x: number; y: number; } = JSON.parse(json);//coordinatesis of type'{ x: number; y: number }', not of type'any'console.log(coordinates);// { x: 10, y: 20 }
- Instead of this:
- When we have a variable to have a type that can't be inferred.
Example: We can use the pipe (|) operator to showcase this.- Instead of this:
let numbers = [-10, 01, 12];let numberAboveZero = false;// type ofnumberAboveZeroinferred by TS is'boolean'for (let i = 0; i < numbers.length; ++i) {
if (numbers[i] > 0) {
numberAboveZero = numbers[i];// error: Type 'number' is not assignable to type 'boolean' ts(2322).
}} - We want this, where we can use more than one type for a variable:
let numbers = [-10, 01, 12];let numberAboveZero: boolean | number;// type ofnumberAboveZeroinferred by TS is either'boolean'or'number'for (let i = 0; i < numbers.length; ++i) {
if (numbers[i] > 0) {
numberAboveZero = numbers[i];// no error.
}}
- Instead of this:
- Type Annotations for Functions: Code we add to tell Typescript what type of arguments a function will receive and what type of values it will return.
- Type Inference for Functions: Typescript tries to figure out what type of value a function will return.
- Therefore, the caveat is that TS will only infer the value that can be returned from a function by reading the function body, but TS will not figure out what type arguments passed into the function are.
- Although, type inference works out the output (return value), we won't use it because we don't want to leave the return type of a function to TS, as we define the functions, we should also be able to write functions where we know what we want to return, and so, we should be defining and annotating what we return from a function.
The difference here is that we're no longer adding type annotations for the variable declaration, instead we're annotating the function itself.