A TypeScript egyike azoknak az eszközöknek, amelyeket az emberek a legjobban szeretnének megtanulni - derül ki a Stack Overflow felmérésből, amely 90 000 fejlesztőt tartalmaz.
A TypeScript népszerűsége, közösségmérete és elfogadottsága az elmúlt években robbanásszerűen megnőtt. Ma még a Facebook Jest projektje is átkerül a TypeScript-be.
Mi a TypeScript?
A TypeScript egy statikusan tipizált JavaScript-készlet, amelynek célja a nagy javascript-alkalmazások fejlesztésének megkönnyítése. A skálázás JavaScript néven is ismert .
Miért érdemes használni a TypeScript-t?
A JavaScript sokat fejlődött az elmúlt években. Ez a legsokoldalúbb platformokon átívelő nyelv, amelyet mind kliens, mind szerver oldalon használnak.
De a JavaScript soha nem jelentett ilyen nagyszabású alkalmazásfejlesztést. Ez egy dinamikus nyelv, típusrendszer nélkül, ami azt jelenti, hogy a változó bármilyen típusú értékkel rendelkezhet, mint például egy string vagy logikai.
A típusrendszerek javítják a kódminőséget, az olvashatóságot, és megkönnyítik a kódalap karbantartását és refaktorát. Ennél is fontosabb, hogy a hibákat a fordítás idején, nem pedig a futás idején lehet elkapni.
Típusrendszer nélkül nehéz a JavaScriptet méretezni, hogy összetett alkalmazásokat építsen ki nagy csapatokkal, ugyanazon a kódon dolgozva.
A TypeScript garanciát nyújt a kód különböző részei között a fordítás idején. A fordítói hibák általában pontosan megmondják, hol történt valami és mi a baj, míg a futásidejű hibákhoz veremkövetés társul, amelyek félrevezetőek lehetnek, és jelentős időt töltenek el a hibakeresési munkával.
TypeScript profik
- Fogja le a lehetséges hibákat a fejlesztési ciklus elején.
- Kezelje a nagy kódbázisokat.
- Könnyebb refaktorálás.
- Megkönnyíti a csapatmunkát - Ha a kódban szereplő szerződések erősebbek, akkor a különböző fejlesztők könnyebben mozoghatnak be és ki a kódbázisból, anélkül, hogy akaratlanul feltörnék a dolgokat.
- Dokumentáció - a típusok valamilyen dokumentációt tartalmaznak, amelyet a jövőbeli ön és más fejlesztők követhetnek.
TypeScript hátrányok
- Ez valami tanulnivaló - kompromisszum a rövid távú lassulás, valamint a hatékonyság és a karbantartás hosszú távú javulása között.
- A típushibák következetlenek lehetnek.
- A konfiguráció drasztikusan megváltoztatja viselkedését.
Típusok
Logikai
const isLoading: boolean = false;
Szám
const decimal: number = 8; const binary: number = 0b110;
Húr
const fruit: string = "orange";
Sor
A tömbtípusok a következő két módszer egyikével írhatók fel:
// Most common let firstFivePrimes: number[] = [2, 3, 5, 7, 11]; // Less common. Uses generic types (more on that later) let firstFivePrimes2: Array = [2, 3, 5, 7, 11];
Tuple
A Tuple-típusok lehetővé teszik egy szervezett tömb kifejezését, ahol ismert a rögzített számú elem típusa. Ez azt jelenti, hogy hibát kap
let contact: [string, number] = ['John', 954683]; contact = ['Ana', 842903, 'extra argument'] /* Error! Type '[string, number, string]' is not assignable to type '[string, number]'. */
Bármi
any
kompatibilis a típusrendszer minden és minden típusával, ami azt jelenti, hogy bármi hozzárendelhető hozzá, és bármihez hozzárendelhető. Erőt ad a típusellenőrzés mellőzéséhez.
let variable: any = 'a string'; variable = 5; variable = false; variable.someRandomMethod(); /* Okay, someRandomMethod might exist at runtime. */
Üres
void
a semmiféle típus hiánya. Általában olyan függvény visszatérési típusaként használják, amelyek nem adnak vissza értéket.
function sayMyName(name: string): void { console.log(name); } sayMyName('Heisenberg');
Soha
A never
típus a soha nem előforduló értékek típusát jelenti. Például never
egy függvény visszatérési típusa, amely mindig kivételt hoz, vagy nem éri el a végpontját.
// throws an exception function error(message: string): never { throw new Error(message); } // unreachable end point function continuousProcess(): never { while (true) { // ... } }
Null és undefined
Mindkét undefined
és null
ténylegesen saját típusok elemzi undefined
és null
rendre. Hasonlóan void
, önmagukban nem rendkívül hasznosak, de hasznossá válnak, ha szakszervezeti típusokon belül használják (erről bővebben egy kicsit)
type someProp = string | null | undefined;
Ismeretlen
A TypeScript 3.0 bemutatja az ismeretlen típust, amely a típusbiztos megfelelője any
. Bármi hozzárendelhető unknown
, de unknown
nem rendelhető hozzá máshoz, csak önmagához, és any.
egyetlen művelet sem engedélyezett egy unknown
anélkül, hogy először állítaná vagy szűkítené egy konkrétabb típust.
type I1 = unknown & null; // null type I2 = unknown & string; // string type U1 = unknown | null; // unknown type U2 = unknown | string; // unknown
Írja be az Alias szót
A Type álnév megadja a típusjegyzetek nevét, így több helyen is használható. A következő szintaxissal jönnek létre:
type Login = string;
Unió típusa
A TypeScript lehetővé teszi, hogy egy adathoz több adattípust használjunk. Ezt hívják szakszervezeti típusnak.
type Password = string | number;
Intersection Type
Intersection types are types that combine properties of all of the member types.
interface Person { name: string; age: number; } interface Worker { companyId: string; } type Employee = Person & Worker; const bestOfTheMonth: Employee = { name: 'Peter' age: 39, companyId: '123456'
Interface
Interfaces are like a contract between you and the compiler in which you specify in a single named annotation exactly what properties to expect with its respective type annotations.
Side-note: Interfaces have zero runtime JS impact, it is used solely for type checking.
- You may declare optionalproperties marking those with an
?
, meaning that objects of the interface may or may not define these properties. - You may declare read onlyproperties, meaning that once a property is assigned a value, it cannot be changed.
interface ICircle { readonly id: string; center: { x: number; y: number; }, radius: number; color?: string; // Optional property } const circle1: ICircle = { id: '001', center: { x: 0 }, radius: 8, }; /* Error! Property 'y' is missing in type '{ x: number; }' but required in type '{ x: number; y: number; }'. */ const circle2: ICircle = { id: '002', center: { x: 0, y: 0 }, radius: 8, } // Okay circle2.color = '#666'; // Okay circle2.id = '003'; /* Error! Cannot assign to 'id' because it is a read-only property. */
Extending Interfaces
Interfaces can extend one or more interfaces. This makes writing interfaces flexible and reusable.
interface ICircleWithArea extends ICircle { getArea: () => number; } const circle3: ICircleWithArea = { id: '003', center: { x: 0, y: 0 }, radius: 6, color: '#fff', getArea: function () { return (this.radius ** 2) * Math.PI; }, };
Implementing an Interface
A class implementing an interface needs to strictly conform to the structure of the interface.
interface IClock { currentTime: Date; setTime(d: Date): void; } class Clock implements IClock { currentTime: Date = new Date(); setTime(d: Date) { this.currentTime = d; } constructor(h: number, m: number) { } }
Enums
An enum
(or enumeration) is a way to organise a collection of related values that can be numeric or string values.
enum CardSuit { Clubs, Diamonds, Hearts, Spades } let card = CardSuit.Clubs; card = "not a card suit"; /* Error! Type '"not a card suit"' is not assignable to type 'CardSuit'. */
Under the hood, enums are number-based by default. enum
values start from zero and increment by 1 for each member.
The JavaScript code generated by our previous example:
var CardSuit; (function (CardSuit) { CardSuit[CardSuit["Clubs"] = 0] = "Clubs"; CardSuit[CardSuit["Diamonds"] = 1] = "Diamonds"; CardSuit[CardSuit["Hearts"] = 2] = "Hearts"; CardSuit[CardSuit["Spades"] = 3] = "Spades"; })(CardSuit || (CardSuit = {})); /** * Which results in the following object: * { * 0: "Clubs", * 1: "Diamonds", * 2: "Hearts", * 3: "Spades", * Clubs: 0, * Diamonds: 1, * Hearts: 2, * Spades: 3 * } */
Alternatively enums can be initialised with string values which is a more readable approach.
enum SocialMedia { Facebook = 'FACEBOOK', Twitter = 'TWITTER', Instagram = 'INSTAGRAM', LinkedIn = 'LINKEDIN' }
Reverse Mapping
enum
supports reverse mapping which means we can access the value of a member and also a member name from its value.
Going back to our CardSuit example:
const clubsAsNumber: number = CardSuit.Clubs; // 3 const clubsAsString: string = CardSuit[0]; // 'Clubs'
Functions
You can add types to each of the parameters and then to the function itself to add a return type.
function add(x: number, y: number): number { return x + y; }
Function Overloads
TypeScript allows you to declare function overloads. Basically, you can have multiple functions with the same name but different parameter types and return type. Consider the following example:
function padding(a: number, b?: number, c?: number, d?: any) { if (b === undefined && c === undefined && d === undefined) { b = c = d = a; } else if (c === undefined && d === undefined) { c = a; d = b; } return { top: a, right: b, bottom: c, left: d }; }
The meaning of each parameter changes based on how many parameters are passed into the function. Moreover, this function only expects one, two or four parameters. To create a function overload, you just declare the function header multiple times. The last function header is the one that is actually active within the function body but is not available to the outside world.
function padding(all: number); function padding(topAndBottom: number, leftAndRight: number); function padding(top: number, right: number, bottom: number, left: number); function padding(a: number, b?: number, c?: number, d?: number) { if (b === undefined && c === undefined && d === undefined) { b = c = d = a; } else if (c === undefined && d === undefined) { c = a; d = b; } return { top: a, right: b, bottom: c, left: d }; } padding(1); // Okay padding(1,1); // Okay padding(1,1,1,1); // Okay padding(1,1,1); /* Error! No overload expects 3 arguments, but overloads do exist that expect either 2 or 4 arguments. */
Classes
You can add types to properties and method’s arguments
class Greeter { greeting: string; constructor(message: string) { this.greeting = message; } greet(name: string) { return `Hi ${name}, ${this.greeting}`; } }
Access Modifiers
Typescript supports public
,private
,protected
modifiers, which determine the accessibility of a class member.
- A
public
member works the same as plain JavaScript members and is the default modifier. - A
private
member cannot be accessed from outside of its containing class. - A
protected
member differ from a private as it can also be accessed within deriving classes.
| Accessible on | public | protected | private | | :------------- | :----: | :-------: | :-----: | | class | yes | yes | yes | | class children | yes | yes | no | | class instance | yes | no | no |
Readonly modifier
A readonly
property must be initialised at their declaration or in the constructor.
class Spider { readonly name: string; readonly numberOfLegs: number = 8; constructor (theName: string) { this.name = theName; } }
Parameter properties
Parameter properties lets you create and initialise a member in one place. They are declared by prefixing a constructor parameter with a modifier.
class Spider { readonly numberOfLegs: number = 8; constructor(readonly name: string) { } }
Abstract
The abstract keyword can be used both for classes and for abstract class methods.
- Abstract classes cannot be directly instantiated. They are mainly for inheritance where the class which extends the abstract class must define all the abstract methods.
- Abstract members do not contain an implementation, thus cannot be directly accessed. These members must be implemented in child classes (kinda like an interface)
Type Assertion
TypeScript allows you to override its inferred types in any way you want to. This is used when you have a better understanding of a variable type than the compiler on its own.
const friend = {}; friend.name = 'John'; // Error! Property 'name' does not exist on type '{}' interface Person { name: string; age: number; } const person = {} as Person; person.name = 'John'; // Okay
Originally the syntax for type assertion was
let person = {};
But this created an ambiguity when used in JSX. Therefore it is recommended to use as
instead.
Type assertion are usually used when migrating code from JavaScript and you may know a more accurate type of the variable than what is currently assigned. But assertion can be considered harmful.
Let’s take a look at our Person interface from the previous example. Did you notice something wrong? If you noticed the missing property age, congratulations! The compiler might help you providing autocomplete for properties of Person but it will not complain if you miss any properties.
Type Inference
TypeScript infers types of variables when there is no explicit information available in the form of type annotations.
/** * Variable definitinon */ let a = "some string"; let b = 1; a = b; // Error! Type 'number' is not assignable to type 'string'. // In case of complex objects, TypeScript looks for the most common type // to infer the type of the object. const arr = [0, 1, false, true]; // (number | boolean)[] /** * Function return types */ function sum(x: number, y: number) { return x + y; // infer to return a number }
Type Compatibility
Type compatibility is based on structural typing, which relates types based solely on their members.
The basic rule for structural type is that x
is compatible with y
if y
has at least the same members as x
.
interface Person { name: string; } let x: Person; // Okay, despite not being an implementation of the Person interface let y = { name: 'John', age: 20 }; // type { name: string; age: number } x = y; // Please note that x is still of type Person. // In the following example, the compiler will show an error message as it does not // expect the property age in Person but the result will be as expected: console.log(x.age); // 20
As y
has a member name: string
, it matched the required properties for the Person interface, meaning that x
is a subtype of y
. Thus, the assignment is allowed.
Functions
Number of arguments
In a function call you need to pass in at least enough arguments, meaning that extra arguments will not cause any errors.
function consoleName(person: Person) { console.log(person.name); } consoleName({ name: 'John' }); // Okay consoleName({ name: 'John', age: 20 }); // Extra argument still Okay
Return type
The return type must contain at least enough data.
let x = () => ({name: 'John'}); let y = () => ({name: 'John', age: 20 }); x = y; // OK y = x; /* Error! Property 'age' is missing in type '{ name: string; }' but required in type '{ name: string; age: number; }' */
Type Guard
Type Guards allow you to narrow down the type of an object within a conditional block.
typeof
Using typeof in a conditional block, the compiler will know the type of a variable to be different. In the following example TypeScript understand that outside the conditional block, x
might be a boolean and the function toFixed
cannot be called on it.
function example(x: number | boolean) { if (typeof x === 'number') { return x.toFixed(2); } return x.toFixed(2); // Error! Property 'toFixed' does not exist on type 'boolean'. }
instanceof
class MyResponse { header = 'header example'; result = 'result example'; // ... } class MyError { header = 'header example'; message = 'message example'; // ... } function example(x: MyResponse | MyError) { if (x instanceof MyResponse) { console.log(x.message); // Error! Property 'message' does not exist on type 'MyResponse'. console.log(x.result); // Okay } else { // TypeScript knows this must be MyError console.log(x.message); // Okay console.log(x.result); // Error! Property 'result' does not exist on type 'MyError'. } }
in
The in
operator checks for the existence of a property on an object.
interface Person { name: string; age: number; } const person: Person = { name: 'John', age: 28, }; const checkForName = 'name' in person; // true
Literal Types
Literals are exact values that are JavaScript primitives. They can be combined in a type union to create useful abstractions.
type Orientation = 'landscape' | 'portrait'; function changeOrientation(x: Orientation) { // ... } changeOrientation('portrait'); // Okay changeOrientation('vertical'); /* Error! Argument of type '"vertical"' is not assignable to parameter of type 'Orientation'. */
Conditional Types
A conditional type describes a type relationship test and selects one of two possible types, depending on the outcome of that test.
type X = A extends B ? C : D;
This means that if type A
is assignable to type B
, then X
is the same type as C
. Otherwise X
is the same as type D;
Generic Types
Generic type is a type that must include or reference another type in order to be complete. It enforce meaningful constraints between various variables.
In the following example a function returns an array of whatever type you pass in.
function reverse(items: T[]): T[] { return items.reverse(); } reverse([1, 2, 3]); // number[] reverse([0, true]); // (number | boolean)[]
keyof
The keyof
operator queries the set of keys for a given type.
interface Person { name: string; age: number; } type PersonKeys = keyof Person; // 'name' | 'age'
Mapped Types
Mapped Types allow you to create new types from existing ones by mapping over property types. Each property of the existing type is transformed according to a rule that you specify.
Partial
type Partial = { [P in keyof T]?: T[P]; }
- The generic Partial type is defined with a single type parameter
T
. keyof T
represents the union of all property names ofT
as string literal types.[P in keyof T]?: T[P]
denotes that the type of each propertyP
of typeT
should be optional and transformed toT[P]
.T[P]
represents the type of the propertyP
of the typeT
.
Readonly
As we have covered in the Interface section, TypeScript allows you to create readonly properties. There is a Readonly
type that takes a type T
and sets all of its properties as readonly.
type Readonly = { readonly [P in keyof T]: T[P]; };
Exclude
Exclude
allows you to remove certain types from another type. Exclude
from T
anything that is assignable to T
.
/** * type Exclude = T extends U ? never : T; */ type User = { _id: number; name: string; email: string; created: number; }; type UserNoMeta = Exclude
Pick
Pick
allows you to pick certain types from another type. Pick
from T
anything that is assignable to T
.
/** * type Pick = { * [P in K]: T[P]; * }; */ type UserNoMeta = Pick
infer
You can use the infer
keyword to infer a type variable within the extends
clause of a conditional type. Such inferred type variable can only be used in the true branch of the conditional type.
ReturnType
Gets the return type of a function.
/** * Original TypeScript's ReturnType * type ReturnType any> = T extends (...args: any) => infer R ? R : any; */ type MyReturnType = T extends (...args: any) => infer R ? R : any; type TypeFromInfer = MyReturnType number>; // number type TypeFromFallback = MyReturnType; // any
Let’s break down MyReturnType
:
- The return type of
T
is … - First of all, is
T
a function? - If so, then the type resolves to the inferred return type
R
. - Otherwise the type resolves to
any
.
References & Useful Links
//basarat.gitbooks.io/typescript/
//www.typescriptlang.org/docs/home.html
//www.tutorialsteacher.com/typescript
//github.com/dzharii/awesome-typescript
//github.com/typescript-cheatsheets/react-typescript-cheatsheet
In order to study and give TypeScript a try I’ve build a simple CurrencyConverter app using TS and React-Native with hooks. You can check this project here.
Thanks and congratulations for reading up to this point! If you have any thoughts on this, feel free to leave a comment.
You can find me on Twitter.