Can I Learn Typescript Without Javascript
TypeScript has become increasingly popular over the last few years, and many jobs are now requiring developers to know TypeScript.
But don't be alarmed – if you already know JavaScript, you volition exist able to selection up TypeScript quickly.
Even if you don't plan on using TypeScript, learning information technology will give you a improve understanding of JavaScript – and make you a better developer.
In this article, you lot will learn:
- What is TypeScript and why should I learn it?
- How to gear up upwardly a project with TypeScript
- All of the main TypeScript concepts (types, interfaces, generics, blazon-casting, and more than...)
- How to utilize TypeScript with React
I also made a TypeScript cheat canvass PDF and poster that summarizes this article down to one page. This makes it piece of cake to look up and revise concepts/syntax quickly.
What is TypeScript?
TypeScript is a superset of JavaScript, meaning that it does everything that JavaScript does, but with some added features.
The main reason for using TypeScript is to add static typing to JavaScript. Static typing means that the blazon of a variable cannot be changed at any point in a programme. It tin can prevent a LOT of bugs!
On the other paw, JavaScript is a dynamically typed linguistic communication, meaning variables tin change blazon. Hither's an example:
// JavaScript let foo = "hello"; foo = 55; // foo has inverse type from a string to a number - no trouble // TypeScript let foo = "hello"; foo = 55; // ERROR - foo cannot modify from string to number
TypeScript cannot be understood by browsers, then it has to be compiled into JavaScript by the TypeScript Compiler (TSC) – which we'll discuss soon.
Is TypeScript worth it?
Why you should use TypeScript
- Research has shown that TypeScript tin spot 15% of common bugs.
- Readability – information technology is easier to run into what the code it supposed to do. And when working in a team, information technology is easier to meet what the other developers intended to.
- Information technology'south popular – knowing TypeScript volition enable you lot to utilise to more than good jobs.
- Learning TypeScript volition give you a better agreement, and a new perspective, on JavaScript.
Here's a brusk article I wrote demonstrating how TypeScript can forbid irritating bugs.
Drawbacks of TypeScript
- TypeScript takes longer to write than JavaScript, equally yous have to specify types, so for smaller solo projects it might non be worth using it.
- TypeScript has to be compiled – which can take time, especially in larger projects.
But the extra time that yous have to spend writing more precise code and compiling will be more than than saved by how many fewer bugs yous'll accept in your code.
For many projects – especially medium to large projects – TypeScript will save you lots of time and headaches.
And if you already know JavaScript, TypeScript won't be too difficult to learn. Information technology's a bang-up tool to have in your arsenal.
How to Set up a TypeScript Project
Install Node and the TypeScript Compiler
Get-go, ensure y'all have Node installed globally on your machine.
Then install the TypeScript compiler globally on your motorcar by running the post-obit control:
npm i -thousand typescript
To check if the installation is successful (it will return the version number if successful):
tsc -v
How to Compile TypeScript
Open up your text editor and create a TypeScript file (for example, alphabetize.ts).
Write some JavaScript or TypeScript:
allow sport = 'football'; let id = 5;
We can at present compile this down into JavaScript with the following command:
tsc index
TSC will compile the code into JavaScript and output information technology in a file called index.js:
var sport = 'football'; var id = five;
If you want to specify the name of the output file:
tsc alphabetize.ts --outfile file-name.js
If you want TSC to compile your lawmaking automatically, whenever you make a change, add the "watch" flag:
tsc alphabetize.ts -w
An interesting thing well-nigh TypeScript is that information technology reports errors in your text editor whilst you are coding, simply it will always compile your code – whether there are errors or not.
For example, the following causes TypeScript to immediately written report an fault:
var sport = 'football'; var id = five; id = '5'; // Error: Type 'string' is not assignable to type 'number'.
But if we try to compile this code with tsc index
, the lawmaking will nevertheless compile, despite the fault.
This is an of import property of TypeScript: it assumes that the developer knows more. Even though there'due south a TypeScript error, information technology doesn't get in your way of compiling the code. It tells you lot at that place'due south an error, but it's up to you lot whether you do anything about it.
How to Ready Upwards the ts config File
The ts config file should be in the root directory of your project. In this file we tin specify the root files, compiler options, and how strict we want TypeScript to exist in checking our project.
First, create the ts config file:
tsc --init
You should now have a tsconfig.json
file in the project root.
Here are some options that are good to be enlightened of (if using a frontend framework with TypeScript, near if this stuff is taken care of for you):
{ "compilerOptions": { ... /* Modules */ "target": "es2016", // Alter to "ES2015" to compile to ES6 "rootDir": "./src", // Where to compile from "outDir": "./public", // Where to compile to (usually the folder to be deployed to the web server) /* JavaScript Support */ "allowJs": true, // Allow JavaScript files to be compiled "checkJs": true, // Type cheque JavaScript files and report errors /* Emit */ "sourceMap": true, // Create source map files for emitted JavaScript files (good for debugging) "removeComments": truthful, // Don't emit comments }, "include": ["src"] // Ensure only files in src are compiled }
To compile everything and watch for changes:
tsc -w
Note: when input files are specified on the command line (for example, tsc index
), tsconfig.json
files are ignored.
Types in TypeScript
Primitive types
In JavaScript, a primitive value is data that is non an object and has no methods. There are vii primitive data types:
- string
- number
- bigint
- boolean
- undefined
- null
- symbol
Primitives are immutable: they can't be altered. Information technology is of import not to confuse a primitive itself with a variable assigned a archaic value. The variable may be reassigned a new value, but the existing value can't be inverse in the ways that objects, arrays, and functions tin exist contradistinct.
Here'due south an example:
let name = 'Danny'; name.toLowerCase(); panel.log(proper noun); // Danny - the cord method didn't mutate the string let arr = [one, three, 5, seven]; arr.pop(); console.log(arr); // [1, 3, 5] - the assortment method mutated the array proper name = 'Anna' // Assignment gives the primitive a new (not a mutated) value
In JavaScript, all archaic values (apart from null and undefined) have object equivalents that wrap effectually the primitive values. These wrapper objects are String, Number, BigInt, Boolean, and Symbol. These wrapper objects provide the methods that allow the archaic values to be manipulated.
Back to TypeScript, we can set the type we desire a variable to be be adding : type
(chosen a "type annotation" or a "blazon signature") later declaring a variable. Examples:
let id: number = v; permit firstname: string = 'danny'; let hasDog: boolean = true; let unit of measurement: number; // Declare variable without assigning a value unit = 5;
But information technology's normally best to not explicitly state the type, as TypeScript automatically infers the type of a variable (blazon inference):
let id = 5; // TS knows it'south a number let firstname = 'danny'; // TS knows it's a string let hasDog = true; // TS knows it's a boolean hasDog = 'yes'; // Mistake
We tin can likewise set a variable to be able to be a spousal relationship type. A union type is a variable that can be assigned more than than 1 type:
let historic period: cord | number; age = 26; age = '26';
Reference Types
In JavaScript, most "everything" is an object. In fact (and confusingly), strings, numbers and booleans can exist objects if defined with the new
keyword:
let firstname = new String('Danny'); console.log(firstname); // String {'Danny'}
Just when we talk of reference types in JavaScript, we are referring to arrays, objects and functions.
Caveat: primitive vs reference types
For those that accept never studied primitive vs reference types, let's discuss the fundamental difference.
If a primitive blazon is assigned to a variable, we can call back of that variable as containing the archaic value. Each primitive value is stored in a unique location in memory.
If we take two variables, x and y, and they both comprise primitive data, so they are completely independent of each other:
let x = 2; let y = 1; x = y; y = 100; console.log(10); // 1 (even though y changed to 100, x is withal one)
This isn't the case with reference types. Reference types refer to a memory location where the object is stored.
let point1 = { ten: 1, y: 1 }; permit point2 = point1; point1.y = 100; console.log(point2.y); // 100 (point1 and point2 refer to the same memory address where the point object is stored)
That was a quick overview of primary vs reference types. Check out this commodity if you need a more thorough explanation: Primitive vs reference types.
Arrays in TypeScript
In TypeScript, you can ascertain what type of information an array can contain:
let ids: number[] = [one, 2, three, 4, v]; // can only contain numbers allow names: cord[] = ['Danny', 'Anna', 'Bazza']; // tin can just comprise strings allow options: boolean[] = [true, faux, simulated]; can only contain truthful or false allow books: object[] = [ { name: 'Fooled past randomness', author: 'Nassim Taleb' }, { name: 'Sapiens', author: 'Yuval Noah Harari' }, ]; // tin can just contain objects let arr: any[] = ['hello', 1, true]; // any basically reverts TypeScript dorsum into JavaScript ids.push button(vi); ids.push('7'); // ERROR: Argument of blazon 'cord' is not assignable to parameter of type 'number'.
Y'all tin can use marriage types to define arrays containing multiple types:
let person: (string | number | boolean)[] = ['Danny', 1, true]; person[0] = 100; person[i] = {name: 'Danny'} // Error - person array tin't contain objects
If you lot initialise a variable with a value, information technology's not necessary to explicitly state the type, as TypeScript will infer it:
permit person = ['Danny', 1, true]; // This is identical to above example person[0] = 100; person[1] = { proper noun: 'Danny' }; // Error - person assortment can't contain objects
There is a special blazon of array that tin exist defined in TypeScript: Tuples. A tuple is an array with stock-still size and known datatypes. They are stricter than regular arrays.
let person: [string, number, boolean] = ['Danny', one, true]; person[0] = 100; // Error - Value at index 0 can merely be a cord
Objects in TypeScript
Objects in TypeScript must take all the correct properties and value types:
// Declare a variable chosen person with a specific object type annotation let person: { name: string; location: string; isProgrammer: boolean; }; // Assign person to an object with all the necessary properties and value types person = { name: 'Danny', location: 'UK', isProgrammer: truthful, }; person.isProgrammer = 'Yes'; // ERROR: should exist a boolean person = { name: 'John', location: 'U.s.', }; // ERROR: missing the isProgrammer property
When defining the signature of an object, you volition usually utilise an interface. This is useful if we need to bank check that multiple objects have the same specific backdrop and value types:
interface Person { name: string; location: cord; isProgrammer: boolean; } let person1: Person = { name: 'Danny', location: 'U.k.', isProgrammer: truthful, }; let person2: Person = { name: 'Sarah', location: 'Germany', isProgrammer: false, };
We can also declare office backdrop with function signatures. We can do this using erstwhile-school mutual JavaScript functions (sayHi
), or ES6 arrow functions (sayBye
):
interface Oral communication { sayHi(name: cord): cord; sayBye: (proper name: string) => cord; } let sayStuff: Voice communication = { sayHi: function (name: string) { render `Hi ${proper name}`; }, sayBye: (name: cord) => `Bye ${proper noun}`, }; console.log(sayStuff.sayHi('Heisenberg')); // Howdy Heisenberg console.log(sayStuff.sayBye('Heisenberg')); // Adieu Heisenberg
Note that in the sayStuff
object, sayHi
or sayBye
could be given an pointer office or a common JavaScript function – TypeScript doesn't care.
Functions in TypeScript
We can ascertain what the types the function arguments should be, as well equally the return type of the function:
// Ascertain a role called circle that takes a diam variable of type number, and returns a cord function circle(diam: number): string { render 'The circumference is ' + Math.PI * diam; } console.log(circle(10)); // The circumference is 31.41592653589793
The same role, only with an ES6 arrow office:
const circumvolve = (diam: number): string => { return 'The circumference is ' + Math.PI * diam; }; console.log(circle(10)); // The circumference is 31.41592653589793
Notice how it isn't necessary to explicitly state that circumvolve
is a function; TypeScript infers it. TypeScript also infers the render type of the role, so it doesn't need to be stated either. Although, if the office is big, some developers like to explicitly state the return type for clarity.
// Using explicit typing const circle: Function = (diam: number): string => { return 'The circumference is ' + Math.PI * diam; }; // Inferred typing - TypeScript sees that circle is a part that e'er returns a string, and then no demand to explicitly country it const circumvolve = (diam: number) => { return 'The circumference is ' + Math.PI * diam; };
Nosotros tin add a question mark afterwards a parameter to make it optional. Likewise notice beneath how c
is a union type that can be a number or string:
const add = (a: number, b: number, c?: number | string) => { console.log(c); return a + b; }; console.log(add together(5, 4, 'I could pass a number, cord, or cypher here!')); // I could pass a number, string, or zip here! // 9
A function that returns nil is said to return void – a complete lack of any value. Below, the render blazon of void has been explicitly stated. But again, this isn't necessary equally TypeScript will infer it.
const logMessage = (msg: string): void => { console.log('This is the message: ' + msg); }; logMessage('TypeScript is superb'); // This is the message: TypeScript is superb
If we desire to declare a function variable, simply not ascertain it (say exactly what information technology does), then use a office signature. Below, the function sayHello
must follow the signature after the colon:
// Declare the varible sayHello, and requite information technology a office signature that takes a string and returns goose egg. let sayHello: (name: string) => void; // Ascertain the function, satisfying its signature sayHello = (name) => { console.log('Hello ' + name); }; sayHello('Danny'); // Hello Danny
Dynamic (any) types
Using the any
type, we can basically revert TypeScript dorsum into JavaScript:
let historic period: any = '100'; age = 100; age = { years: 100, months: 2, };
It's recommended to avoid using the any
type equally much equally you can, as it prevents TypeScript from doing its job – and can lead to bugs.
Type Aliases
Blazon Aliases tin reduce code duplication, keeping our lawmaking Dry. Below, we can run across that the PersonObject
type alias has prevented repetition, and acts as a single source of truth for what data a person object should incorporate.
type StringOrNumber = cord | number; blazon PersonObject = { name: string; id: StringOrNumber; }; const person1: PersonObject = { name: 'John', id: 1, }; const person2: PersonObject = { name: 'Delia', id: two, }; const sayHello = (person: PersonObject) => { return 'Hullo ' + person.proper noun; }; const sayGoodbye = (person: PersonObject) => { return 'Seeya ' + person.name; };
The DOM and type casting
TypeScript doesn't have access to the DOM similar JavaScript. This means that whenever we attempt to admission DOM elements, TypeScript is never certain that they actually exist.
The beneath example shows the problem:
const link = document.querySelector('a'); panel.log(link.href); // Fault: Object is possibly 'null'. TypeScript can't be sure the anchor tag exists, as it tin can't access the DOM
With the non-naught assertion operator (!) we can tell the compiler explicitly that an expression has value other than null
or undefined
. This is tin be useful when the compiler cannot infer the type with certainty, but nosotros have more information than the compiler.
// Hither nosotros are telling TypeScript that we are certain that this ballast tag exists const link = document.querySelector('a')!; console.log(link.href); // www.freeCodeCamp.org
Notice how we didn't take to state the blazon of the link
variable. This is because TypeScript can clearly encounter (via Type Inference) that it is of type HTMLAnchorElement
.
Just what if nosotros needed to select a DOM element by its class or id? TypeScript tin't infer the type, equally it could be anything.
const form = document.getElementById('signup-form'); panel.log(form.method); // Fault: Object is possibly 'goose egg'. // ERROR: Property 'method' does not be on type 'HTMLElement'.
Higher up, we get ii errors. We need to tell TypeScript that we are sure form
exists, and that we know information technology is of type HTMLFormElement
. We practise this with type casting:
const form = document.getElementById('signup-form') every bit HTMLFormElement; console.log(form.method); // post
And TypeScript is happy!
TypeScript too has an Outcome object built in. So, if we add a submit effect listener to our course, TypeScript will give us an fault if we telephone call whatsoever methods that aren't role of the Event object. Cheque out how cool TypeScript is – information technology can tell us when nosotros've made a spelling mistake:
const form = certificate.getElementById('signup-form') as HTMLFormElement; course.addEventListener('submit', (east: Event) => { e.preventDefault(); // prevents the folio from refreshing panel.log(east.tarrget); // Mistake: Property 'tarrget' does not be on type 'Event'. Did you mean 'target'? });
Classes in TypeScript
We can ascertain the types that each piece of data should exist in a class:
form Person { name: string; isCool: boolean; pets: number; constructor(due north: string, c: boolean, p: number) { this.name = n; this.isCool = c; this.pets = p; } sayHello() { render `Hi, my proper noun is ${this.name} and I have ${this.pets} pets`; } } const person1 = new Person('Danny', false, 1); const person2 = new Person('Sarah', 'aye', vi); // Mistake: Argument of type 'cord' is not assignable to parameter of type 'boolean'. console.log(person1.sayHello()); // Hullo, my name is Danny and I have 1 pets
Nosotros could and then create a people
array that only includes objects synthetic from the Person
class:
let People: Person[] = [person1, person2];
Nosotros tin add admission modifiers to the properties of a class. TypeScript also provides a new access modifier called readonly
.
grade Person { readonly name: string; // This property is immutable - it can only be read private isCool: boolean; // Can just access or change from methods within this form protected email: string; // Can access or modify from this class and subclasses public pets: number; // Can access or modify from anywhere - including exterior the class constructor(n: string, c: boolean, due east: cord, p: number) { this.name = n; this.isCool = c; this.email = east; this.pets = p; } sayMyName() { console.log(`Your non Heisenberg, y'all're ${this.name}`); } } const person1 = new Person('Danny', false, 'dan@eastward.com', 1); panel.log(person1.name); // Fine person1.name = 'James'; // Error: read only console.log(person1.isCool); // Error: private property - only attainable inside Person course console.log(person1.email); // Error: protected belongings - just accessible within Person class and its subclasses console.log(person1.pets); // Public property - so no problem
Nosotros can brand our code more concise by constructing class properties this way:
class Person { constructor( readonly name: string, private isCool: boolean, protected email: cord, public pets: number ) {} sayMyName() { console.log(`Your not Heisenberg, you're ${this.name}`); } } const person1 = new Person('Danny', faux, 'dan@eastward.com', 1); console.log(person1.name); // Danny
Writing it the to a higher place style, the properties are automatically assigned in the constructor – saving us from having to write them all out.
Note that if nosotros omit the access modifier, past default the property will exist public.
Classes can as well be extended, just like in regular JavaScript:
class Programmer extends Person { programmingLanguages: string[]; constructor( name: string, isCool: boolean, email: string, pets: number, pL: cord[] ) { // The super call must supply all parameters for base (Person) class, every bit the constructor is not inherited. super(name, isCool, email, pets); this.programmingLanguages = pL; } }
For more than on classes, refer to the official TypeScript docs.
Modules in TypeScript
In JavaScript, a module is merely a file containing related lawmaking. Functionality can be imported and exported betwixt modules, keeping the code well organized.
TypeScript besides supports modules. The TypeScript files will compile down into multiple JavaScript files.
In the tsconfig.json
file, change the following options to support modern importing and exporting:
"target": "es2016", "module": "es2015"
(Although, for Node projects you very likely want "module": "CommonJS"
– Node doesn't yet support modern importing/exporting.)
At present, in your HTML file, alter the script import to be of type module:
<script blazon="module" src="/public/script.js"></script>
We can at present import and export files using ES6:
// src/hullo.ts export office sayHi() { console.log('How-do-you-do there!'); } // src/script.ts import { sayHi } from './hullo.js'; sayHi(); // Howdy there!
Note: always import as a JavaScript file, even in TypeScript files.
Interfaces in TypeScript
Interfaces define how an object should look:
interface Person { name: string; age: number; } part sayHi(person: Person) { console.log(`How-do-you-do ${person.name}`); } sayHi({ name: 'John', age: 48, }); // Hi John
You tin can also define an object type using a type allonym:
type Person = { name: string; age: number; }; function sayHi(person: Person) { console.log(`Hi ${person.proper name}`); } sayHi({ name: 'John', age: 48, }); // Hi John
Or an object blazon could be defined anonymously:
function sayHi(person: { name: cord; historic period: number }) { panel.log(`Hi ${person.name}`); } sayHi({ proper noun: 'John', age: 48, }); // Howdy John
Interfaces are very like to type aliases, and in many cases you can utilize either. The key stardom is that type aliases cannot be reopened to add new backdrop, vs an interface which is always extendable.
The following examples are taken from the TypeScript docs.
Extending an interface:
interface Animal { name: string } interface Conduct extends Creature { honey: boolean } const carry: Bear = { proper name: "Winnie", love: truthful, }
Extending a type via intersections:
blazon Animal = { name: string } blazon Conduct = Brute & { honey: boolean } const carry: Carry = { name: "Winnie", honey: true, }
Adding new fields to an existing interface:
interface Animal { name: string } // Re-opening the Brute interface to add a new field interface Brute { tail: boolean } const dog: Animal = { name: "Bruce", tail: true, }
Here's the central difference: a type cannot be changed after being created:
type Animal = { proper noun: string } type Animal = { tail: boolean } // Mistake: Duplicate identifier 'Animal'.
As a rule of thumb, the TypeScript docs recommend using interfaces to ascertain objects, until you need to use the features of a type.
Interfaces can as well define function signatures:
interface Person { name: cord age: number speak(judgement: string): void } const person1: Person = { proper name: "John", age: 48, speak: sentence => console.log(sentence), }
You may be wondering why we would use an interface over a class in the above example.
One advantage of using an interface is that it is only used past TypeScript, not JavaScript. This means that it won't become compiled and add together bloat to your JavaScript. Classes are features of JavaScript, then information technology would become compiled.
Also, a class is substantially an object mill (that is, a design of what an object is supposed to look like so implemented), whereas an interface is a structure used solely for type-checking .
While a class may have initialized properties and methods to help create objects, an interface substantially defines the properties and type an object tin can have.
Interfaces with classes
We can tell a class that it must contain certain properties and methods past implementing an interface:
interface HasFormatter { format(): string; } class Person implements HasFormatter { constructor(public username: cord, protected password: string) {} format() { return this.username.toLocaleLowerCase(); } } // Must exist objects that implement the HasFormatter interface let person1: HasFormatter; let person2: HasFormatter; person1 = new Person('Danny', 'password123'); person2 = new Person('Jane', 'TypeScripter1990'); console.log(person1.format()); // danny
Ensure that people
is an array of objects that implement HasFormatter
(ensures that each person has the format method):
let people: HasFormatter[] = []; people.push(person1); people.push(person2);
Literal types in TypeScript
In addition to the full general types string
and number
, we can refer to specific strings and numbers in type positions:
// Union blazon with a literal blazon in each position let favouriteColor: 'ruddy' | 'blue' | 'dark-green' | 'yellow'; favouriteColor = 'blueish'; favouriteColor = 'crimson'; // ERROR: Blazon '"red"' is not assignable to type '"red" | "blue" | "green" | "yellow"'.
Generics
Generics allow you to create a component that can work over a variety of types, rather than a single ane, which helps to make the component more than reusable.
Let'south become through an example to show you what that means...
The addID
function accepts whatsoever object, and returns a new object with all the backdrop and values of the passed in object, plus an id
property with random value betwixt 0 and 1000. In short, it gives any object an ID.
const addID = (obj: object) => { let id = Math.floor(Math.random() * one thousand); return { ...obj, id }; }; permit person1 = addID({ name: 'John', historic period: xl }); console.log(person1.id); // 271 console.log(person1.proper noun); // Fault: Belongings 'proper name' does not exist on blazon '{ id: number; }'.
As you lot can see, TypeScript gives an error when we endeavor to access the proper noun
property. This is considering when we pass in an object to addID
, we are not specifying what backdrop this object should have – so TypeScript has no thought what properties the object has (it hasn't "captured" them). So, the simply belongings that TypeScript knows is on the returned object is id
.
So, how can nosotros pass in whatever object to addID
, but all the same tell TypeScript what properties and values the object has? Nosotros can use a generic, <T>
– where T
is known as the blazon parameter:
// <T> is just the convention - e.thou. nosotros could use <X> or <A> const addID = <T>(obj: T) => { permit id = Math.floor(Math.random() * thou); return { ...obj, id }; };
What does this do? Well, now when nosotros laissez passer an object into addID
, we have told TypeScript to capture the type – and so T
becomes any type we laissez passer in. addID
will at present know what properties are on the object we pass in.
But, we now accept a problem: anything can exist passed into addID
and TypeScript volition capture the type and report no problem:
let person1 = addID({ proper noun: 'John', age: 40 }); let person2 = addID('Sally'); // Pass in a cord - no problem panel.log(person1.id); // 271 console.log(person1.name); // John console.log(person2.id); panel.log(person2.name); // ERROR: Property 'name' does non exist on blazon '"Sally" & { id: number; }'.
When nosotros passed in a string, TypeScript saw no result. It but reported an error when we tried to access the name
property. So, we need a constraint: we need to tell TypeScript that only objects should be accustomed, past making our generic type, T
, an extension of object
:
const addID = <T extends object>(obj: T) => { let id = Math.floor(Math.random() * thou); return { ...obj, id }; }; let person1 = addID({ name: 'John', age: 40 }); let person2 = addID('Sally'); // ERROR: Argument of type 'string' is not assignable to parameter of type 'object'.
The mistake is defenseless straight abroad – perfect... well, non quite. In JavaScript, arrays are objects, so we can all the same go away with passing in an assortment:
let person2 = addID(['Emerge', 26]); // Pass in an array - no problem console.log(person2.id); // 824 console.log(person2.proper name); // Mistake: Belongings 'name' does not exist on type '(cord | number)[] & { id: number; }'.
We could solve this by saying that the object statement should accept a name belongings with string value:
const addID = <T extends { name: string }>(obj: T) => { let id = Math.floor(Math.random() * k); return { ...obj, id }; }; allow person2 = addID(['Emerge', 26]); // Mistake: statement should have a name belongings with string value
The type tin also exist passed in to <T>
, as beneath – but this isn't necessary most of the time, as TypeScript will infer it.
// Below, we have explicitly stated what blazon the argument should be between the angle brackets. permit person1 = addID<{ name: string; age: number }>({ name: 'John', historic period: 40 });
Generics permit y'all to accept blazon-safety in components where the arguments and return types are unknown ahead of time.
In TypeScript, generics are used when we want to depict a correspondence betwixt 2 values. In the above instance, the return blazon was related to the input type. We used a generic to describe the correspondence.
Another case: If we need a function that accepts multiple types, it is ameliorate to apply a generic than the any
type. Below shows the upshot with using whatsoever
:
function logLength(a: any) { console.log(a.length); // No error render a; } allow hi = 'How-do-you-do world'; logLength(hello); // 11 let howMany = 8; logLength(howMany); // undefined (but no TypeScript error - surely we desire TypeScript to tell the states we've tried to access a length holding on a number!)
We could effort using a generic:
function logLength<T>(a: T) { console.log(a.length); // Mistake: TypeScript isn't certain that `a` is a value with a length property return a; }
At least we are now getting some feedback that we tin can use to tighten up our lawmaking.
Solution: utilize a generic that extends an interface that ensures every argument passed in has a length property:
interface hasLength { length: number; } role logLength<T extends hasLength>(a: T) { panel.log(a.length); render a; } let how-do-you-do = 'Hello earth'; logLength(hello); // xi let howMany = eight; logLength(howMany); // Fault: numbers don't have length properties
We could as well write a function where the argument is an assortment of elements that all take a length belongings:
interface hasLength { length: number; } role logLengths<T extends hasLength>(a: T[]) { a.forEach((element) => { console.log(chemical element.length); }); } let arr = [ 'This string has a length prop', ['This', 'arr', 'has', 'length'], { material: 'plastic', length: thirty }, ]; logLengths(arr); // 29 // 4 // 30
Generics are an awesome feature of TypeScript!
Generics with interfaces
When nosotros don't know what blazon a sure value in an object will be alee of fourth dimension, we can use a generic to pass in the type:
// The type, T, will be passed in interface Person<T> { proper noun: string; age: number; documents: T; } // Nosotros have to pass in the type of `documents` - an array of strings in this case const person1: Person<string[]> = { name: 'John', age: 48, documents: ['passport', 'bank argument', 'visa'], }; // Again, nosotros implement the `Person` interface, and pass in the blazon for documents - in this case a string const person2: Person<string> = { proper name: 'Delia', age: 46, documents: 'passport, P45', };
Enums in TypeScript
Enums are a special feature that TypeScript brings to JavaScript. Enums allow u.s.a. to define or declare a collection of related values, that can be numbers or strings, as a prepare of named constants.
enum ResourceType { Volume, AUTHOR, Pic, Manager, PERSON, } panel.log(ResourceType.Book); // 0 console.log(ResourceType.AUTHOR); // 1 // To start from 1 enum ResourceType { BOOK = ane, AUTHOR, FILM, Director, PERSON, } console.log(ResourceType.Book); // ane console.log(ResourceType.Author); // 2
By default, enums are number based – they store cord values every bit numbers. Only they tin can besides be strings:
enum Direction { Upward = 'Upward', Right = 'Right', Down = 'Down', Left = 'Left', } panel.log(Direction.Right); // Right panel.log(Direction.Down); // Down
Enums are useful when we take a prepare of related constants. For instance, instead of using non-descriptive numbers throughout your code, enums make code more readable with descriptive constants.
Enums can also forbid bugs, as when yous type the proper noun of the enum, intellisense will pop up and give you the listing of possible options that tin be selected.
TypeScript strict mode
Information technology is recommended to accept all strict type-checking operations enabled in the tsconfig.json
file. This will cause TypeScript to report more errors, just will help prevent many bugs from creeping into your application.
// tsconfig.json "strict": true
Let's talk over a couple of the things that strict manner does: no implicit whatever, and strict cipher checks.
No implicit any
In the function beneath, TypeScript has inferred that the parameter a
is of any
type. As you tin see, when we pass in a number to this function, and effort to log a name
property, no fault is reported. Not good.
office logName(a) { // No error?? console.log(a.proper noun); } logName(97);
With the noImplicitAny
option turned on, TypeScript will instantly flag an error if we don't explicitly state the type of a
:
// Error: Parameter 'a' implicitly has an 'whatever' type. role logName(a) { console.log(a.name); }
Strict zippo checks
When the strictNullChecks
option is false, TypeScript effectively ignores cypher
and undefined
. This can lead to unexpected errors at runtime.
With strictNullChecks
set to true, naught
and undefined
have their own types, and you'll get a type error if you assign them to a variable that expects a concrete value (for example, string
).
permit whoSangThis: string = getSong(); const singles = [ { song: 'bear on of grey', creative person: 'grateful dead' }, { song: 'paint it black', artist: 'rolling stones' }, ]; const single = singles.discover((southward) => s.song === whoSangThis); panel.log(single.creative person);
Above, singles.notice
has no guarantee that it will find the vocal – but we have written the code equally though it e'er volition.
Past setting strictNullChecks
to true, TypeScript will heighten an fault because nosotros oasis't made a guarantee that single
exists before trying to apply information technology:
const getSong = () => { return 'vocal'; }; permit whoSangThis: string = getSong(); const singles = [ { vocal: 'touch of grey', artist: 'grateful expressionless' }, { vocal: 'paint it black', creative person: 'rolling stones' }, ]; const single = singles.find((s) => s.vocal === whoSangThis); console.log(single.artist); // Fault: Object is maybe 'undefined'.
TypeScript is basically telling usa to ensure single
exists earlier using it. Nosotros need to bank check if information technology isn't null
or undefined
first:
if (single) { console.log(unmarried.artist); // rolling stones }
Narrowing in TypeScript
In a TypeScript plan, a variable can move from a less precise blazon to a more precise blazon. This process is called type narrowing.
Here's a simple example showing how TypeScript narrows down the less specific type of string | number
to more specific types when we use if-statements with typeof
:
role addAnother(val: string | number) { if (typeof val === 'cord') { // TypeScript treats `val` as a string in this block, so we tin use cord methods on `val` and TypeScript won't shout at us render val.concat(' ' + val); } // TypeScript knows `val` is a number hither render val + val; } console.log(addAnother('Woooo')); // Woooo Woooo panel.log(addAnother(20)); // 40
Some other example: below, nosotros have defined a union type called allVehicles
, which tin can either be of type Plane
or Railroad train
.
interface Vehicle { topSpeed: number; } interface Train extends Vehicle { carriages: number; } interface Plane extends Vehicle { wingSpan: number; } type PlaneOrTrain = Plane | Train; role getSpeedRatio(v: PlaneOrTrain) { // In here, we want to return topSpeed/carriages, or topSpeed/wingSpan console.log(v.carriages); // ERROR: 'carriages' doesn't exist on type 'Aeroplane' }
Since the office getSpeedRatio
is working with multiple types, we need a manner of distinguishing whether 5
is a Aeroplane
or Railroad train
. We could do this by giving both types a mutual distinguishing property, with a literal cord value:
// All trains must now have a type property equal to 'Railroad train' interface Railroad train extends Vehicle { type: 'Train'; carriages: number; } // All trains must now have a type belongings equal to 'Plane' interface Plane extends Vehicle { type: 'Plane'; wingSpan: number; } type PlaneOrTrain = Plane | Train;
Now we, and TypeScript, can narrow downwardly the blazon of v
:
function getSpeedRatio(v: PlaneOrTrain) { if (v.type === 'Train') { // TypeScript now knows that `five` is definitely a `Train`. Information technology has narrowed down the type from the less specific `Aeroplane | Train` type, into the more specific `Train` type return v.topSpeed / v.carriages; } // If information technology'due south not a Railroad train, TypeScript narrows downward that `v` must be a Aeroplane - smart! return v.topSpeed / five.wingSpan; } allow bigTrain: Train = { type: 'Train', topSpeed: 100, carriages: 20, }; console.log(getSpeedRatio(bigTrain)); // v
Bonus: TypeScript with React
TypeScript has full support for React and JSX. This means we tin use TypeScript with the 3 near common React frameworks:
- create-react-app (TS setup)
- Gatsby (TS setup)
- Next.js (TS setup)
If you require a more custom React-TypeScript configuration, you could setup Webpack (a module bundler) and configure the tsconfig.json
yourself. But almost of the time, a framework will do the job.
To setup up create-react-app with TypeScript, for example, but run:
npx create-react-app my-app --template typescript # or yarn create react-app my-app --template typescript
In the src binder, we tin at present create files with .ts
(for regular TypeScript files) or .tsx
(for TypeScript with React) extensions and write our components with TypeScript. This volition then compile down into JavaScript in the public folder.
React props with TypeScript
Below, we are proverb that Person
should be a React functional component that accepts a props object with the props proper noun
, which should exist a string, and age
, which should be a number.
// src/components/Person.tsx import React from 'react'; const Person: React.FC<{ name: cord; age: number; }> = ({ name, age }) => { return ( <div> <div>{proper noun}</div> <div>{age}</div> </div> ); }; export default Person;
Just most developers prefer to utilise an interface to specify prop types:
interface Props { name: string; age: number; } const Person: React.FC<Props> = ({ name, age }) => { return ( <div> <div>{name}</div> <div>{age}</div> </div> ); };
We can then import this component into App.tsx
. If nosotros don't provide the necessary props, TypeScript will requite an mistake.
import React from 'react'; import Person from './components/Person'; const App: React.FC = () => { return ( <div> <Person name='John' historic period={48} /> </div> ); }; export default App;
Here are a few examples for what we could have as prop types:
interface PersonInfo { name: string; age: number; } interface Props { text: string; id: number; isVeryNice?: boolean; func: (proper name: cord) => string; personInfo: PersonInfo; }
React hooks with TypeScript
useState()
We can declare what types a country variable should exist by using angle brackets. Below, if we omitted the angle brackets, TypeScript would infer that greenbacks
is a number. Then, if want to enable it to also be null, we have to specify:
const Person: React.FC<Props> = ({ name, age }) => { const [cash, setCash] = useState<number | goose egg>(i); setCash(nada); return ( <div> <div>{proper noun}</div> <div>{historic period}</div> </div> ); };
useRef()
useRef
returns a mutable object that persists for the lifetime of the component. We can tell TypeScript what the ref object should refer to – below nosotros say the prop should be a HTMLInputElement
:
const Person: React.FC = () => { // Initialise .current belongings to null const inputRef = useRef<HTMLInputElement>(null); return ( <div> <input type='text' ref={inputRef} /> </div> ); };
For more information on React with TypeScript, checkout these awesome React-TypeScript cheatsheets.
Useful resources & further reading
- The official TypeScript docs
- The Net Ninja'due south TypeScript video series (crawly!)
- Ben Awad's TypeScript with React video
- Narrowing in TypeScript (a very interesting characteristic of TS that yous should learn)
- Function overloads
- Primitive values in JavaScript
- JavaScript objects
Thanks for reading!
Hope that was useful. If you made it to here, yous now know the main fundamentals of TypeScript and can showtime using information technology in your projects.
Again, yous can as well download my 1-page TypeScript crook canvas PDF or order a physical poster.
For more than from me, you lot can observe me on Twitter and YouTube.
Cheers!
Learn to code for complimentary. freeCodeCamp's open source curriculum has helped more than than 40,000 people become jobs as developers. Get started
Can I Learn Typescript Without Javascript,
Source: https://www.freecodecamp.org/news/learn-typescript-beginners-guide/
Posted by: wilsontheyind.blogspot.com
0 Response to "Can I Learn Typescript Without Javascript"
Post a Comment