Небольшая заметка о TypeScript и его возможностях.

Что такое TypeScript?

TypeScript — это супер набор JavaScript.

Почему TypeScript:

  1. TypeScript уменьшает количество ошибок, используя типы, которые помогают выявлять ошибки во время компиляции, а не во время выполнения;
  2. TypeScript позволяет вам использовать новый JS уже сегодня;

Типы TypeScript

TypeScript использует типы для анализа вашего кода на наличие ошибок и для понимания того, какие значения связаны с переменными.

С помощью TypeScript вы можете явно указывать типы для идентификаторов, таких как переменные, функции, объекты и т. д. Это называется —аннотации типов. Просто используйте синтаксис : type

// e.g.
let someNumber: number;
let someString: string;
let someBoolean: boolean = true;
someNumber = 1;
someString = 1; // Compile error

Если вы забудете явно указать тип, то TypeScript выведет тип, который вы указали. Это называется вывод типа. Давайте посмотрим, например:

let someNumber = 0;
// It is equivalent to the line below
let someNumber: number = 0;

Когда мне нужно использовать вывод типа или аннотации типов?

Рекомендуется использовать аннотации типов, когда:

  • Когда вы создаете переменную и позже присваиваете ей значение;
  • Когда вам нужна переменная, которую нельзя вывести;
  • Когда функция возвращает тип any и вам нужно указать значение;

Во всех остальных случаях следует максимально использовать вывод типов.

Есть две категории типов

Примитивные типы:

  • строка — представляет собой текстовые данные;
// string examples
let someString1: string = 'Hello World'; // single quotes
let someString2: string = "Hello World"l // double quotes
let someString3 = `Hello
                   World`; // multi-line string with backtick
let someString4 = `${someString1}`; // string interpolation
  • число — представляет числовые значения;
// decimal numbers
let someNumber1: number;
let someNumber2 = 5.55;
let x: number = 10,
    y: number = 20;
// binary numbers
let someBinaryNumber1 = 0b99;
let someBinaryNumber2: number = 0b100;
// big int
let someBigIntNumber = 9007199254740991n;
// hexadecimal numbers
let someHexDecimalNumber = 0XA;
  • boolean — имеет значения true или false
let someBoolean1 = true;
let someBoolean2: boolean;
someBoolean2 = true;
  • null — имеет только одно значение: null
  • undefined — имеет только одно значение: undefined. Также это значение по умолчанию для неинициализированной переменной
  • символ — представляет собой уникальную константу

Типы объектов:

  • объект — представляет все значения, которые не относятся к примитивным типам.
let person: object;
person = {
   name: 'Stas',
   age: 30
};

Мы можем явно указать свойства объекта person с помощью приведенного ниже синтаксиса.

let person: {
   name: string,
   age: number
};
person = {
   name: 'Stas',
   age: 30
};
// or we can do it in this way
let person: {
   name: string,
   age: number
} = {
   name: 'Stas',
   age: 30
};

Если вы попытаетесь получить доступ к свойству, которого нет в объекте person, вы получите сообщение об ошибке.

  • массив — это упорядоченный список данных
let someArray: number[]; 
// to add element we can use next syntax
someArray.push(1);
// or
someArray[0] = 1;

Мы можем объявить массив с начальными элементами, и TypeScript выведет массив fruits как массив строк.

let fruits = ['banana', 'mango'];

Это эквивалентно следующему:

let fruits: string[];
fruits = ['banana', 'mango'];

Конечно, вы не можете добавить какой-либо другой тип к объявленному типу массива. Компилятор TS выдаст ошибку.

fruits.push(100); // error
  • enum — список именованных констант.
enum Week {
   Monday, // 0
   Tuesday, // 1
   Wednesday, // ...
   Thursday,
   Friday,
   Saturday,
   Sunday
}
// you can change start number explicitly
enum Week { 
   Monday = 1,
   ...
}

Рекомендуется использовать перечисления, когда у вас есть небольшой набор фиксированных значений, которые тесно связаны и известны во время компиляции.

  • функции — это строительные блоки читаемого, поддерживаемого и многократно используемого кода.
function functionName(par1: type, par2: type, ...): returnType {
   //some code
}

Вы также можете объявить тип функции, который имеет параметры и тип возвращаемого значения.

(par: type, par,...) => returnType 
// example
let stringConcat: (str1: string, str2: string) => string;
stringConcat = function(str1: string, str2: string) {
   return str1 + str2;
}
// or you can do it like this
let stringConcat: (str1: string, str2: string) => string = 
   function(s1: string, s2: string) {
      return s1 + s2;
   }
}

Функции могут иметь необязательные параметры

// use '?' after parameter name
function functionName(par1: type, par2?: type): returnType {
   //some code
}

Или параметры по умолчанию

// use 'par:type = value' syntax
function functionName(par1: type, par2: type = 'Hello'): returnType {
   //some code
}

Вы также можете перегрузить функцию

function add(a: number, b: number): number; 
function add(a: string, b: string): string; 
function add(a: any, b: any): any { return a + b; }
class Person {
   name;
   age;
   
   constructor(name, age) {
      this.name = name;
      this.age = age;
   }
   getPersonInfo() {
      return `${name} ${age}`;
   }
}
let person = new Person('Stas', 30);
console.log(person.getPersonInfo());

Или вы можете использовать аннотации типа для свойств и методов в классе

class Person {
   name: string;
   age: number;
   
   constructor(name: string, age: number) {
      this.name = name;
      this.age = age;
   }
   getPersonInfo(): string {
      return `${name} ${age}`;
   }
}
let person = new Person('Stas', 30);
console.log(person.getPersonInfo());

Класс TS имеет три модификатора доступа к свойствам и методам:

  1. privatemodifier — разрешает доступ к свойству в пределах того же класса.
  2. protectedmodifier — разрешает доступ к свойству в пределах того же класса и подклассов.
  3. publicmodifier — разрешает доступ к свойству из любого места.

В классе вы можете использовать модификатор readonly, который позволяет помечать свойства как неизменяемые. С помощью readonly вы можете присвоить значение только в объявлении свойства или конструкторе.

class Person {
   readonly name: string;
   
   constructor(name: string) {
      this.name = name;
   }
}

Если вы хотите наследовать один класс от другого класса, используйте ключевое слово extends, и если у родительского класса есть конструктор, который инициализирует некоторые свойства (имя, возраст), вам необходимо инициализировать эти свойства в конструкторе класса Employee, вызвав его родительский класс. конструктор через super().

class Person {
   name: string;
   age: number;
   
   constructor(name: string, age: number) {
      this.name = name;
      this.age = age;
   }
 
   getPersonInfo(): string {
      return `${name} ${age}`;
   }
}
class Employee extends Person {
   jobTitle: string;
   constructor(
      name: string,
      age: number,
      jobTitle: string) {
         super(name, age);
         this.jobTitle = jobTitle;
   }
   describe() {
      return super.getPersonInfo() + this.jobTitle;
   }
}
let employee = new Employee('Stas', 30, 'Developer');
console.log(employee.describe());

Класс может иметь статические свойства и методы, общие для всех экземпляров класса. Используйте statickeyword для объявления статического свойства или метода.

class Person {
   static counter: number;
   
   constructor() {
      Person.counter++;
   }
   
   static getPersonCount() {
      return Person.counter;
   }
}
let person1 = new Person();
let person2 = new Person();
console.log(Person.getPersonCount()); // 2

Еще одна хорошая особенность TypeScript — абстрактный класс, абстрактный класс имеет как минимум один абстрактный метод, и для его использования вам нужно его наследовать и предоставить реализацию для абстрактных методов.
Конечно, вы не можете создать экземпляр абстрактного класса.

abstract class Person {
   constructor(name: string, age: number) {}
   
   abstract getInfo(): string;
}
class Manager extends Employee {
   constructor(name: string, age: number) {
      super(name, age);
   }
   
   getInfo(): string {
       return this.name + '  ' + this.age;
   }
}
  • интерфейс — определите контракты в коде и предоставьте явные имена для проверки типов. Интерфейс может содержать только для чтения или необязательные свойства, и вы можете использовать интерфейс как тип класса, который заключает контракт между несвязанными классами или как типы функций.
interface Person {
   name: string,
   age: number
}
function getInfo(person: Person) {
   return person.name + ' ' + person.age;
}
// optional properties 
interface Person {
   name: string,
   age: number,
   jobTitle?: string
}
function getInfo(person: Person) {
   if(person.jobTitle) {
      return person.name + ' ' + person.age + '-' person.jobTitle;
   }
   
   return person.name + ' ' + person.age;
}
//readonly properties
interface Person {
   readonly name: string,
   age: number
}
let person: Person;
person = {
   name: 'Stas',
   age: 30
}
person.name = 'David'; // error
person.age = 31; // ok

Через интерфейс вы можете описать типы функций

interface StringFormat {
   (str: string, isLower: boolean): string
}
let format: StringFormat;
format = function(str: string, isLower: boolean) {
   return isLower 
      ? str.toLocaleLowerCase()
      : str.toLocaleUpperCase()
}
console.log(format('Hello World', true)); // hello world

Наконец, класс может реализовать интерфейс через implements

interface Json {
   toJson(): string
}
class Employee implements Person {
   name: string;
   constructor(name: string, age: number) {
      this.name = name;
      this.age = age;
   }
   
   toJson() {
      return JSON.stringify(this);
   }
}
let employee = new Employee('Stas', 30);
console.log(employee.toJson()); // {"name":"Stas","age":"30"}

Последнее, что касается интерфейсов — он может расширять один или несколько существующих интерфейсов.

interface A { 
   a(): void 
}  
interface B extends A {     
   b(): void 
}
// B interface has two methods: a() and b()

Особые типы

  • any — используйте, когда вы не знаете тип на момент написания программы (некоторые значения могут исходить из API) или при миграции с JS на TS.
  • void — используйте, когда функция не возвращает никакого значения.
function(message: string): void {
   console.log(message);
}
  • никогда — тип, который не содержит значения, это означает, что вы не можете присвоить какое-либо значение переменной с типом never. never используйте, когда у вас есть функция, которая всегда выдает ошибку. Например:
function someFunction(message: string): never {
   throw new Error(message);
}

Или вы можете использовать nevertype, если у вас неопределенный цикл

let infinityLoop = function createInfinityLoop() {
   while(true) {
      console.log('.');
   }
}
  • union — позволяет хранить в переменной значение одного или нескольких типов
let someUnionValue: string | number;
someUnionValue = 1; // ok
someUnionValue = 'Hello'; // ok
someUnionValue = false; // error
  • псевдонимы — позволяет вам определять новые имена для существующих типов
type newAliasesType = string | number; 
let someVariable: newAliasesType;
  • literal — позволяет определить тип, который принимает только определенные строковые значения.
let statusLiteral: 'approve' | 'reject' | 'pending';
let myStatus: statusLiteral;
myStatus = 'approve'; // ok
myStatus = 'hello'; // error
  • пересечение — создает новый тип путем объединения нескольких существующих типов, и новый тип будет иметь все функции существующих типов. Порядок не имеет значения
type typeAB = typeA & typeB; // definition
// example
type hybridVariable = number & string;
let someVariable: hybridVariable = '3'; // ok
someVariable = 3; // ok
someVariable = false // error

Обратите внимание, что uniontype и intersection не совпадают.

Типовая защита и приведение

  1. используйте typeof, если вам нужно проверить тип переменной
let a: number;
if (typeof a === 'number') { ... }

2. используйте instanceof для проверки экземпляра класса

class Person1 {...}
class Person2 {...}
type Person = Person1 | Person2;
function showInfo(person: Person) {
    if (person instanceof Person1) {...}
    if (person instanceof Person2) {...}
}

3. используйте in, если вам нужно проверить наличие свойства у объекта

class Person {
   name: string;
   ...
}
function showInfo(person: Person) {
   if('name' in person) {...}
}

TS позволяют преобразовать переменную из одного типа в другой. Используйте ключевое слово as или оператор <> для приведения типов.

let a: typeA;
let b = a as typeB;
// or 
let a: typeA;
let b = <typeB>a;

Общие

Обобщения позволяют вам писать повторно используемые и обобщенные формы функций, классов и интерфейсов.

Преимущества дженериков:

  1. Избавьтесь от приведения типов
  2. Реализация алгоритмов дженериков
  3. Проверка типа кредитного плеча во время компиляции.
function getRandomElement<T>(items: T[]): T {
   let randomIndex = Math.floor(Math.random() * items.length);
   return items[randomIndex];
}
let numbers = [1, 5, 7, 4, 2, 9]; 
let randomNumberEle = getRandomElement<number>(numbers);  console.log(randomNumberEle);
let strings = ['a', 'b', 'c'];
let randomStringEle = getRandomElement<string>(strings);
console.log(randomStringEle);

Вы можете использовать дженерики с несколькими типами

function merge<U, V>(obj1: U, obj2: V) { 
    return {
         ...obj1,
         ...obj2
    };
}

И обозначьте ограничение, вы используете ключевое слово extends

function merge<U extends object, V extends object>(obj1: U, obj2: V) {...}

Вдохновлено — https://www.typescripttutorial.net

Узнали что-то новое?

Если вам понравилась эта статья, вы можете купить мне чашечку кофе, и я выпью ее, когда буду писать следующую статью :)