Добро пожаловать в TypeScript — язык, давший новую жизнь JavaScript! TypeScript можно считать крутым кузеном JavaScript, который всегда в курсе последних трендов. TypeScript добавляет статические типы, делая код более надежным и легким для отладки. Эта его супервозможность позволяет отлавливать ошибки до того, как они успеют нарушить работу приложение.

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

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

TypeScript обладает возможностью вывода типов. Это означает, что даже если типы не объявлены явно, компилятор TypeScript может вывести их на основе контекста. JavaScript, будучи динамически типизированным, не имеет такой возможности.

Аннотация типов

Аннотация типов позволяет TypeScript быть предельно понятным.

Аннотации типов — своеобразный способ TypeScript спросить: «Вы уверены в этом?». Они позволяют указать точный тип переменной, параметра функции или возвращаемого значения. Это что-то вроде подробной карты кода!

// Переменные: вы указываете тип переменной, когда объявляете ее.
let message: string = "Hello, World!";

// Параметры функции: вы указываете типы параметров, которые принимает функция.
function greet(name: string): void {
console.log("Hello, " + name);
}

// Возвращаемые значения функции: вы указываете тип значения, которое возвращает функция.
function add(a: number, b: number): number {
return a + b;
}

Получение в TypeScript ответов API

Приходилось ли вам, получая данные из API, недоумевать: «Стоп, что это такое?» Представьте, что TypeScript может испытывать то же самое! Вам нужно помочь ему понять данные, определив типы.

// получение данных через API
const apiResponse = {
id: 1,
name: 'rania elmorabet',
birthDate: "2002-23-12"
};

const userProfile: {
id: number;
name: string;
birthDate: string;
} = apiResponse;

// или можно написать так, поскольку это объект:

const {id, name, birthDate}: {
id: number;
name: string;
birthDate: string;
} = apiResponse;

Вывод типов

Вывод типов — это функция, при которой компилятор автоматически определяет тип переменной, возвращаемого значения функции или других выражений без явных аннотаций типа.

Переменные: когда вы объявляете переменную и инициализируете ее значением, TypeScript определяет ее тип на основе этого значения.

// TypeScript выводит тип как строку
let message = "Hello, World!";

Время для примеров!

Вот пример, демонстрирующий аннотацию и вывод типов:

// Аннотация типа 
let count: number = 5;

// Вывод типа
let name = "Rania"; // TypeScript infers the type as string

// Функция с аннотацией типа
function multiply(a: number, b: number): number {
return a * b;
}

// Функция с выводом типа
function greetUser(userName: string) {
console.log("Hello, " + userName);
}

let result = multiply(2, 3); // TypeScript выводит результат в виде числа

Объект, массив, кортеж, пустой тип (void)

Вот как можно собрать некоторые необходимые элементы TypeScript:

// Объект
const userProfile: {username:string; age: number; isMarried:boolean; hobbies: string[] } = {
username: 'rania',
age:21,
isMarried:false,
hobbies:['Singing', 'Cooking']
};

// Массив
const numbers:number[] =[1,2,3,4,5];

// Кортеж
const List:[number,string,{username:string},boolean] = [1, 'Hello', {username: 'rania'}, true];

// Пустой тип
function logMessage(message:string): void {
console.log(message)
}

Оператор spread и деструктурирование

Оператор spread и деструктурирование в TypeScript делают работу с объектами и массивами намного интереснее!

// Оператор spread
const userProfile : {username:string; age:number; isMarried:boolean}= {
username: 'Rania',
age: 21,
isMarried: false,
};
const newUser:{username:string; age:number; isMarried:boolean; address:string} =
{...userProfile, address: 'main street'}
console.log(newUser)

// Деструктуризация
const {username, age, isMarried} : {username:string; age:number; isMarried:boolean}= {
username: 'Rania',
age: 21,
isMarried: false,
};

Интерфейсы

Интерфейс — способ описания структуры объекта. Он определяет свойства и методы, которыми должен обладать объект. Вместо того, чтобы писать их рядом с самим объектом, мы поступаем следующим образом:

interface IUser {
username:string;
age:number;
isMarried:boolean
}

const UserProfile: IUser= {
username: 'Rania',
age: 21,
isMarried: false,
};

В случае использования оператора spread, пишем следующее:

interface IUser {
username:string;
age:number;
isMarried:boolean;
address?:string; // добавляем '?'; это означает, что я могу добавить адрес, а могу и не добавлять.
}

const UserProfile: IUser= {
username: 'Rania',
age: 21,
isMarried: false,
};
const newUser : IUser = {...UserProfile, address: 'main street'};

Или можно сделать это таким образом:

interface IUser {
username:string;
age:number;
isMarried:boolean;
}

interface InewUser {
username:string;
age:number;
isMarried:boolean;
}

const UserProfile: IUser= {
username: 'Rania',
age: 21,
isMarried: false,
};
const newUser : InewUser = {...UserProfile, address: 'main street'};

Чтобы не повторяться, используйте ключевое слово extends. Оно позволяет наследовать свойства от другого интерфейса.

Расширение интерфейсов

Ключевое слово extends используется для создания нового класса или интерфейса, который наследует свойства и методы от существующего класса или интерфейса. Это позволяет повторно использовать код.

interface IUser {
username:string;
age:number;
isMarried:boolean;
address?:string;
}
interface InewUser extends IUser{
address?:string;
}
const UserProfile: IUser= {
username: 'Rania',
age: 21,
isMarried: false,
};
const newUser : InewUser = {...UserProfile, address: 'main street'};

Ключевое слово type 

Ключевое слово type в TypeScript используется для определения псевдонимов типов. Псевдоним типа создает новое имя для типа, который может быть примитивом (primitive), объектом (object), массивом (array), объединением (union), пересечением (intersection) или любым другим допустимым типом TypeScript.

Вот базовый пример использования ключевого слова type для определения псевдонима типа:

type ID = number;
let userId: ID = 12345;

Тип union

type Status = "success" | "error" | "loading";

let currentStatus: Status = "loading";

Тип intersection

type Address = {
street: string;
city: string;
};

type Contact = {
email: string;
phone: string;
};

type UserContact = Address & Contact;

let contact: UserContact = {
street: "123 Main St",
city: "Anytown",
email: "[email protected]",
phone: "555-1234",
};

Тип Record

Прежде чем говорить о Record, стоит упомянуть о readonly — модификаторе, используемом для создания свойств в объектах или массивах, которые не могут быть изменены после их первоначального назначения. Он помогает гарантировать неизменность данных.

interface IUser {
username:string;
readonly age:number;
isMarried:boolean;
}
const UserProfile: IUser= {
username: 'Rania',
age: 21,
isMarried: false,
};
UserProfile.age = 40;

После использования readonly в интерфейсе age вы получите ошибку при попытке изменить возраст.

Record — это вспомогательный тип (utility type), который представляет тип объекта с определенными свойствами и соответствующими им значениями. Он особенно полезен, когда надо определить объект с известным набором ключей и единым типом для всех значений, связанных с этими ключами.

Вот базовый синтаксис для Record:

Record<Keys, Type>
type StatusMessages = Record<'success' | 'error' | 'pending', string>;

const statusMessages: StatusMessages = {
success: 'Operation was successful.',
error: 'An error occurred.',
pending: 'Operation is pending.',
};

Оператор Keyof

Оператор keyof в TypeScript используется для создания типа union ключей объектного типа. Это мощная функция для более динамичной и гибкой работы с типами.

interface IUser {
username: string,
age:number,
address:string
}
type UserKeys = keyof IUser;

const user : IUser = {
username: 'Rania',
age: 21,
address: '10 Main Street',
};

function getProperty(obj :IUser , Key: UserKeys) {
return obj[Key];
}
console.log(getProperty(user, "username"))
console.log(getProperty(user, "age"))
console.log(getProperty(user, "address"))

Функция с деструктуризацией массива

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

function printCoordinates([x, y]: [number, number]): void {
console.log(`The coordinates are X: ${x}, Y: ${y}`);
}

const point: [number, number] = [10, 20];
printCoordinates(point); // Вывод: координаты будут следующие - X: 10, Y: 20

Функция с rest-параметрами

Параметры rest позволяют функции принимать неограниченное количество аргументов в виде массива.

function sum(...numbers: number[]): number {
return numbers.reduce((acc, num) => acc + num, 0);
}

console.log(sum(1, 2, 3)); // Вывод: 6
console.log(sum(4, 5, 6, 7)); // Вывод: 22

Тип enum

Тип enum (перечисление) позволяет определить набор именованных констант.

enum Direction {
Up = 1,
Down,
Left,
Right
}

function move(direction: Direction): void {
switch (direction) {
case Direction.Up:
console.log("Moving up");
break;
case Direction.Down:
console.log("Moving down");
break;
case Direction.Left:
console.log("Moving left");
break;
case Direction.Right:
console.log("Moving right");
break;
}
}

move(Direction.Up); // Вывод: Moving up (движение вверх)

Дженерики 

Дженерики (generics) предоставляют возможность создавать многократно используемые компоненты, работающие с различными типами. Они позволяют писать функции, классы или интерфейсы, которые могут работать с различными типами данных, сохраняя при этом безопасность типов.

function identity<T>(arg: T): T {
return arg;
}

console.log(identity<number>(5)); // Вывод: 5
console.log(identity<string>("Hello")); // Вывод: Hello

В данном примере функция identity — это дженерик, который принимает параметр типа T и возвращает значение того же типа T. Синтаксис <T> определяет параметр типа T, который может быть любого типа.

Тип Partial

Вспомогательный тип Partial позволяет сделать все свойства типа опциональными.

Это полезно, когда нужно создать объект, у которого не обязательно должны быть определены все свойства.

interface User {
id: number;
name: string;
age: number;
}

function updateUser(user: Partial<User>): void {
// логика пользователя
}

updateUser({ name: "Rania" }); // Только обновление имени

В этом примере функция updateUser принимает Partial<User>, то есть она может принимать объект, который имеет некоторые или все свойства интерфейса User. Это удобно, когда необходимо обновить только определенные поля пользователя.

Сигнатуры индекса

Сигнатуры индекса (index signatures) позволяют определить свойства, доступ к которым можно получить с помощью строкового или числового индекса, что полезно, когда набор свойств неизвестен заранее.

interface StringDictionary {
[key: string]: string;
}

const dictionary: StringDictionary = {
firstName: "Rania",
lastName: "El Morabet"
};

console.log(dictionary.firstName); // Вывод: Rania
console.log(dictionary["lastName"]); // Вывод: El Morabet

В данном примере интерфейс StringDictionary имеет индексную сигнатуру [key: string]: string. Это означает, что он может иметь свойства с любым строковым ключом, и все значения будут иметь тип string. Объект dictionary соответствует этому интерфейсу, позволяя получать доступ к своим свойствам, используя либо точечную, либо скобочную нотацию.

Компиляция TypeScript

Итак, вы написали кое-что с помощью TypeScript и теперь готовы увидеть, как он преобразуется в обычный JavaScript. Вот как это можно сделать (поверьте, это проще, чем вы думаете):

tsc
Компилирует текущий проект (tsconfig.json в рабочем каталоге).

tsc app.ts util.ts
Игнорируя tsconfig.json, компилирует конкретные файлы с параметрами компилятора по умолчанию.

tsc -b
Создание составного проекта в рабочем каталоге.

tsc --init
Создает tsconfig.json с рекомендуемыми настройками в рабочем каталоге.

tsc -p ./path/to/tsconfig.json
Компилирует проект TypeScript, расположенный по указанному пути.

tsc --help --all
Расширенная версия этой информации, показывающая все возможные варианты компилятора.

tsc --noEmit

tsc --target esnext
Компилирует текущий проект с дополнительными настройками.

Читайте также:

Читайте нас в Telegram, VK и Дзен


Перевод статьи RANIA EL MORABET: TypeScript : The JavaScript Upgrade We Didn’t Know We Needed

Предыдущая статьяСети Колмогорова-Арнольда (KAN) могут навсегда изменить мир ИИ
Следующая статьяНам нужно визуальное программирование. Нет, не то, о котором вы подумали