Рубріки: Теория

TypeScript: понятное руководство для новичков

Андрій Денисенко

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


 

Содержание:
1. Что такое TypeScript?
2. Отличия TypeScript от JavaScript
3. Установка TypeScript
4. Как работает TypeScript?
5. Особенности TypeScript
6. Пишем приложение на TypeScript
7. Преимущества и недостатки TypeScript
Итоги

1. Что такое TypeScript?

TypeScript — это язык со статической типизацией на основе JavaScript. Он создан в 2012 году в компании Microsoft. Разработкой TypeScript занимался Андрес Гейлсберг (тот самый, который был ведущим архитектором C# и Delphi, а также создал Turbo Pascal).

TypeScript — это JavaScript с дополнительными возможностями

Строгая типизация

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

В TypeScript можно явно указать тип переменной. Когда тип переменной определен, ей невозможно присвоить значение, которое относится к другому типу.

Число остается числом

Расширенные возможности ООП

В TypeScript реализованы дополнительные возможности ООП, такие как интерфейсы, поля объекта и модификаторы доступа.

Интерфейс определяется с помощью ключевого слова interface.

// Определить интерфейс домашнего животного
interface IPet {
    name: string,
    species: string,
    breed: string,
    speak: () => string
}
 
// Создать пса
var dog:IPet = {
    name: "Ritchie Blaсkdog",
    species: "dog",
    breed: "hellhound",
    speak: ():string =>
        {return "Woof! Woof-woof-woof!"}
}

Поля и модификаторы доступа:

class User {
    // Поля будут приватными
    private name: string;
    private password: string;
 
    constructor(name: string, password: string){
        this.name = name;
        this.password = password;
    }
 
    // Доступ для чтения открыт
    public get Name(): string{
        return this.name;
    }
 
    public get Password(): string{
        return this.password;
    }
}

2. Отличия TypeScript от JavaScript

JavaScript создан для написания небольших сценариев веб-страниц. Чем крупнее приложение, тем более громоздким становится код на JS.

Поэтому был создан TypeScript, который упрощает разработку широкомасштабных приложений.

Из-за нестрогой типизации в JavaScript многие ошибки видны только во время выполнения. Это неудобно и, в конце концов, неприятно. Приходится тратить много времени на поиск таких ошибок.

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

Ошибки видны во время разработки

В TypeScript реализована поддержка интерфейсов и модификаторов доступа, обобщений, модулей, пространств имен и окружений (ambients). В JavaScript этого нет.

Код JavaScript — интерпретируемый язык. Он воспринимается браузерами напрямую. TypeScript компилируется в JavaScript.

3. Установка TypeScript

Осваивать язык программирования лучше всего на практике. Для тестирования небольших фрагментов кода существует TypeScript Playground. Здесь вы можете набирать код, просматривать ошибки, журнал, код JavaScript, в который скомпилирован ваш код на TypeScript, настраивать среду по своему усмотрению, экспортировать код и скопировать ссылку, чтобы поделиться им.

Бонус — код копируется с форматированием.

Удобная «игровая площадка» для знакомства с TypeScript и тестирования сниппетов

Для разработки проектов вам понадобится установить компилятор TypeScript. Его можно установить с помощью менеджера зависимостей для Node.js: npm, yarn или pnpm.

Предположим, что Node.js и менеджер зависимостей npm установлены. Создадим папку проекта и в командной строке запустим такую команду:

npm install typescript --save-dev

С помощью этой команды установки создается локальная копия TypeScript в папке проекта. Для рабочих проектов рекомендуется использовать именно такую установку.

Можно установить TypeScript и глобально:

npm install -g typescript

В этом случае команда tsc будет доступна в любом расположении. Глобальную установку можно использовать для одноразовых испытаний.

4. Как работает TypeScript?

Теперь проверим установку. В этой же папке создадим файл dog.ts (файлы TypeScript имеют расширение ts).

Добавим в него код из примера про пса, который приведен выше, и сохраним файл. В командной строке введем:

npx tsc dog.ts

После выполнения команды получим файл dog.js с таким кодом:

// Создать пса
var dog = {
   name: "Ritchie Blaсkdog",
   species: "dog",
   breed: "hellhound",
   speak: function () { return "Woof! Woof-woof-woof!"; }
};

С помощью Deno можно выполнять файлы TypeScript прямо в консоли. Добавим в файл dog.ts такую строку:

console.log(`${dog.name} says ${dog.speak()}`)

Запустим команду:

deno run dog.ts

И получим результат:

5. Особенности TypeScript

Объявление типов

В TypeScript можно объявить переменную четырьмя способами: с указанием типа и значения, с указанием только типа, с указанием только значения — и без типа, и без значения.

// Объявить тип и значение в одном операторе.
var amount:number = 10000.00;
 
/* Объявить тип, не присваивая значения.
  По умолчанию переменной будет присвоено значение undefined. */var amount:number;
 
/* Объявить значение, не задавая тип.
  Переменная получит тип присвоенного значения. */var amount = 10000.00;
 
/* Не указывать ни типа, ни значения.
  В этом случае переменная получит тип any и значение undefined. */var amount;

После того как объявлен тип переменной, ей невозможно присвоить значение другого типа.

Тип остается тем же, который был задан

Но если, все-таки, необходимо изменить тип переменной, то можно воспользоваться утверждением типа (type assertion). Для этого нужно заключить требуемый тип в символы < > и поместить перед переменной или выражением.

var x = '1'
var y:number = <number> <any> x
console.log(typeof(y))

Приведенный выше код выведет в консоль строку «string».

Рассмотрим типы данных, определенные в TypeScript.

Any

Тип any — это надтип для всех типов в TypeScript, динамический тип. Его использование подразумевает отказ от проверки типа переменной.

Встроенные типы

В приведенной ниже таблице перечислены встроенные типы TypeScript и даны их описания.

Ключевое слово Описание
number 64-разрядные числовые значения с плавающей точкой. Используются для представления как целых, так и дробных чисел.
string Последовательность символов Unicode.
boolean Логическое значение: true или false
void Тип возврата для функций, которые не возвращают значения
null Явное указание отсутствия значения объекта.
undefined Значение, присваиваемое всем переменным до их инициализации

Пользовательские типы

В приведенной ниже таблице перечислены пользовательские типы TypeScript и даны их описания.

Тип Описание
массив (array)

и кортеж (tuple)

Эти типы позволяют хранить множеству несколько значений в заданной последовательности. Массив состоит из элементов одного типа, а кортеж может содержать значения нескольких разных типов. Для обращения к каждому элементу массива используется метод TypeScript forEach().
перечисление (enum) Как и в C#, enum дает возможность присвоить удобочитаемые имена последовательности числовых значений.
объединение (union) Начиная с TypeScript 1.4 можно комбинировать типы, благодаря чему переменная может принимать значение, относящееся к одному из них.
объект (object) Представляет собой любое значение, не относящееся к примитивному типу.

Литералы типов

Типами в TypeScript могут быть не только строки вообще (string) или числа вообще (number). Конкретное число или конкретная строка тоже может быть типом. Например, типом может быть «8», а может быть «красный». Если переменная объявлена с литералом типа, то она не может принимать никакие другие значения.

Рассмотрим это на примере кода:

let red:"красный" = "красный";
red = "красный";
red = "зеленый";

При попытке его скомпилировать получим ошибку:

И для чего же нужна переменная с уникальным типом и единственным значением?

Сама по себе она не особо полезна. Дело в применении.

Например, в CSS есть свойства, которые принимают лишь несколько определенных значений. Свойство alignment может принимать значения left, right или center.
Если объединить литералы типов left, right и center (которые объявлены как строки) в union, то получим двойную пользу:

  • будут приниматься лишь допустимые значения;
  • они будут передаваться в виде строк, которые можно вставить в код (не нужно ветвление, чтобы сопоставить значения из объединения со строковыми значениями).

Наглядно это будет выглядеть так:

function setText(msg: string, alignment: "left" | "right" | "center") {
 // ...
}
setText("Готово!", "left");
setText("Ошибка!", "bottom");

Компилятор не дает ошибиться:

Шаблоны литералов типов

Шаблоны — это надстройка над литералами типов. Благодаря объединениям они могут разворачиваться во множество строк без необходимости в циклах.

В шаблонах литеральных типов используется тот же синтаксис, что и в шаблонах литералов строк JavaScript, но с указанием типов, а не переменных.

Если же тип определен в виде объединения, то типу присваивается каждый возможный литерал из комбинаций членов объединения.

Если добавить еще один критерий, например, валюту, то будет создано множество из всех возможных комбинаций.

То есть, весь список значений для этого типа будет таким:

type cardsByCurrencies = "USD_prepaid_visa" | "USD_prepaid_mastercard"
| "USD_credit_visa" | "USD_credit_mastercard" | "EUR_prepaid_visa"
| "EUR_prepaid_mastercard" | "EUR_credit_visa" | "EUR_credit_mastercard"

Необходимо также заметить, что и в литералах типов, равно как и в шаблонах, может использоваться вывод типов. Подробнее см. в документации.

Классы

TypeScript — это объектно-ориентированный JavaScript. Он поддерживает классы, интерфейсы и т. п. TypeScript получил поддержку классов от ES6.

Объявление класса

Для объявления класса используется ключевое слово class.

class User {
// Область видимости класса
}

Приведенный выше код компилируется в такой код JavaScript.

var User = /** @class */ (function () {
function User() {
}
return User;
}());

Приведем пример объявления класса с полями, конструктором и функцией.

class Person {
    // Поле
    firstName: string;
    lastName:  string;
 
    // Конструктор
    constructor(firstName:string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
 
    // Функция
    logFullName(): void {
        console.log(`${this.firstName} ${this.lastName}`);
    }
}

На JavaScript этот код будет выглядеть так:

var Person = /** @class */ (function () {
   // Конструктор
   function Person(firstName, lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   // Функция
   Person.prototype.logFullName = function () {
       console.log("".concat(this.firstName, " ").concat(this.lastName));
   };
   return Person;
}());

Создание экземпляра класса

Экземпляр класса в TypeScript создается с помощью ключевого слова new.

var p = new Person('Mickey', 'Mouse');

Доступ к полям и функциям

Для обращения к полям и функциям используется запись с точкой.

p.firstName;
p.logFullName();

Сведем все воедино:

class Person {
    // Поле
    firstName: string;
    lastName:  string;
 
    // Конструктор
    constructor(firstName:string, lastName: string) {
        this.firstName = firstName;
        this.lastName = lastName;
    }
 
    // Функция
    logFullName(): void {
        console.log(`${this.firstName} ${this.lastName}`);
    }
}
 
var p = new Person('Mickey', 'Mouse');
console.log(`Имя:     ${p.firstName}`);
console.log(`Фамилия: ${p.lastName}`);
console.log('Полное имя:');
p.logFullName();

Код JavaScript:

var Person = /** @class */ (function () {
   // Конструктор
   function Person(firstName, lastName) {
       this.firstName = firstName;
       this.lastName = lastName;
   }
   // Функция
   Person.prototype.logFullName = function () {
       console.log("".concat(this.firstName, " ").concat(this.lastName));
   };
   return Person;
}());
var p = new Person('Mickey', 'Mouse');
console.log("\u0418\u043C\u044F:     ".concat(p.firstName));
console.log("\u0424\u0430\u043C\u0438\u043B\u0438\u044F: ".concat(p.lastName));
console.log('Полное имя:');
p.logFullName();

Вывод:

Как видите, если вы знакомы с ООП и JavaScript, все довольно просто.

Пространства имен

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

Пример

Файл conflict.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Конфликт имен в JavaScript</title>
    <script src="scr1.js"></script>
    <script src="scr2.js"></script>
    <script>
        console.log("Inline script:");
        console.log(text);
    </script>
</head>
<body>
    <div id="target1"></div>
    <div id="target2"></div>
    <div id="target3"></div>
</body>
</html>

Файл scr1.js

var text = "Text from scr1.js";
console.log(text);

Файл scr2.js

var text = "Text from scr2.js";
console.log(text);

Первым импортируется файл scr1.js, вторым — scr2.js, поэтому вложенный сценарий, объявленный после импорта двух внешних, получит переменную text со значением, которое присвоено в scr2.js.

TypeScript устраняет этот недостаток за счет пространств имен. Термин «пространство имен» введен в TypeScript 1.5. До этой версии в TypeScript были внутренние и внешние модули. Теперь внутренние модули называются пространствами имен, а внешние — просто модулями.

Для определения пространства имен используется ключевое слово namespace. Классы и интерфейсы, к которым будут обращаться извне модуля, необходимо пометить ключевым словом export.

namespace MyNameSpaceName {
export MyClassName { }
export IMyInterfaceName { }
}

Для доступа к классу или интерфейсу, которые находятся в другом пространстве имен, используется запись с точкой: namespaceName.className.

Если пространство имен находится в другом файле, то необходимо указать ссылку на этот файл, используя запись с тремя косыми чертами:

/// <reference path = "SomeFileName.ts" />

Объявим переменные с одним и тем же именем в разных пространствах имен.

Файл animals.ts

namespace Animals {
export let count = 12;
}

Файл people.ts

namespace People {
export let count = 3;
}

Файл howmany.ts

/// <reference path = "./people.ts" />
/// <reference path = "./animals.ts" />
console.log(People.count);
console.log(Animals.count);

Скомпилируем эти файлы в код JavaScript. Для этого передадим команде tsc параметр --outFile, за которым указывается имя целевого файла JavaScript, а затем перечисляются файлы TypeScript.

npx tsc --outFile howmany.js howmany.ts animals.ts people.ts

Получим следующий код JavaScript:

var People;
(function (People) {
   People.count = 3;
})(People || (People = {}));
var Animals;
(function (Animals) {
   Animals.count = 12;
})(Animals || (Animals = {}));
/// <reference path = "./people.ts" />
/// <reference path = "./animals.ts" />
console.log(People.count);
console.log(Animals.count);

Соответственно, результаты вывода значения count будут разными: 3 и 12.

У пространств имен в TypeScript есть такие возможности:

  • пространство имен можно расположить в отдельном файле, который подключается с помощью ссылки;
  • пространства имен можно вкладывать друг в друга. Вложенные пространства имен помечаются ключевым словом export;
  • в одном файле можно объявить несколько пространств имен;
  • пространство имен можно разбить на несколько файлов, приводя ссылку на основной файл в каждом дополнительном;
  • чтобы не набирать долгие пути к классам, можно объявить псевдоним импортируемого класса. Например, если у нас есть пространство имен Data, в которое вложено пространство имен Animal с классом Dog, то псевдоним можно объявить так: import dog = Data.Animal.Dog. Тогда можно будет обращаться к полям и методам этого класса через префикс dog.

Модули

Модули помогают упорядочить код, написанный на TypeScript. До TypeScript 1.5 модули делились на внутренние и внешние. С версии 1.5 внутренние модули называются пространствами имен, а внешние — собственно модулями.

Модулем считается любой файл с импортом или экспортом на высшем уровне. Модули выполняются в собственной области видимости.

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

Чтобы интерфейс, класс, переменную или функцию можно было использовать в другом файле, перед объявлением добавляется ключевое слово export (как это делалось в примере с пространствами имен). Но вместо того, чтобы добавлять его к каждому объявлению, можно использовать более простую и наглядную запись, объединяя классы, интерфейсы и т. п.:

export { Dog, move };

Модуль импортируется с помощью ключевого слова import:

import { Dog, move } from "./myfile.js";

При импорте можно указывать псевдонимы с помощью ключевого слова as (его также можно использовать для создания псевдонима при экспорте):

import { Dog, move as myMove } from "./myfile.js";

Можно импортировать модуль целиком в одно пространство имен:

import * as mymodule from "./myfile.js";

В конце концов, можно указать один класс как экспортируемый по умолчанию. Для этого после слова export указывается слово default. При этом данный класс будет импортироваться по умолчанию, даже если в операторе импорта указан псевдоним.

Совместимость с JavaScript. Окружение (Ambient)

JavaScript — это подмножество TypeScript. Казалось бы, если в сценарии TS есть код JS, то компилятор TypeScript примет его без проблем. Это справедливо, если код JavaScript написан с учетом безопасности с точки зрения типов.

Другое дело, если вы используете большие библиотеки JS или сторонние, например jQuery, Node.js или AngularJS (именно AngularJS, потому что библиотека Angular написана на TypeScript). Чтобы обеспечить безопасность типов и ввод с автодополнением (intellisense), TypeScript-программисту придется приложить нечеловеческие усилия.

Объявления окружения (ambient) помогают беспроблемно встраивать библиотеки JS в TypeScript. Определения окружений по соглашению помещаются в файл определения типов в расширением d.ts, например ThirdPartyLib.d.ts. Этот файл не компилируется в JavaScript.

Переменные и модули определяются так:

declare module Module_Name {
 export class SomeClass{
   doSmth(times:number) : number;
 }
}

В файле окружения нет реализации. В нем объявляются типы. Затем они включаются в файл TypeScript следующим образом:

/// <reference path = "ThirdPartyLib.d.ts" />

В результате при попытке передать функции doSmth строковое значение будет выдано сообщение об ошибке — еще во время компиляции.

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

6. Пишем приложение на TypeScript

Создадим приложение для предварительного просмотра сообщений. Сообщения будут делиться на три вида: информационные, предупреждения и сообщения об ошибке. В зависимости от выбранного типа сообщения меняется его стиль.

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

Файл msg.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <title>Сообщения</title>
    <link rel="stylesheet" type="text/css" href="style.css">
    <script type="text/javascript" src="script.js"></script>
</head>
<body>
    <h1>Сообщения</h1>
    <div class="param">
        <div>
            <p>Заголовок</p>
            <input type="text" id="header" value="Заголовок">
        </div>
        <div>
            <p>Текст</p>
            <textarea id="body" rows="10">Текст сообщения</textarea>
        </div>
 
        <div>
            <p>Тип</p>
            <select id="type">
                <option value="info">Сообщение</option>
                <option value="warning">Предупреждение</option>
                <option value="error">Ошибка</option>
            </select>
        </div>
 
        <button >

Настроим внешний вид элементов. Стиль элементов будет состоять из двух частей: типа (error|warning|info) и назначения: заголовка и тела сообщения.

Файл style.css

body {
 font-family: Verdana, sans-serif;
 color: #336699;
}
 
div {
 margin: 5px;
 padding: 5px; 
}
 
.param {
 width:300px;
 display: inline-block;
 float: left;
 background-color: #336699;
 color: #ffffff;
 border-radius: 10px;
}
 
.result {
 display: inline-block;
 float: left;
 width: 300px;
 background-color: #ffffff;
 color: #336699;
 border: 1px solid #003366;
 border-radius: 10px;
}
 
.message {
 border: 1px solid;
 border-radius: 10px;
 padding: 0px;
}
 
.header {
 font-weight: bold;
 font-size: larger;
}
 
.body {
 font-style: italic;
}
 
.info {
   color: #009900;
   border-color: #009900;
}
 
.warning {
   color: #FF8C00;
   border-color: #FF8C00;
}
 
.error {
   color: #990000;
   border-color: #990000;
}

По нажатию кнопки будем менять весь стиль элемента разом. Поскольку из скрипта можно менять только его отдельные свойства, заменим весь innerHTML элемента result. Для каждого элемента укажем класс, подставив литеральный тип перед идентификатором класса назначения элемента.

Файл script.ts

// Типы сообщений помещаем в объединение
type msgTypes = "info" | "warning" | "error";
// Части сообщения тоже помещаем в объединение
type msgParts = "header" | "body";
 
class Message {
 header: string;
 body: string;
 type: msgTypes;
 
 constructor(header: string, body: string, type: msgTypes) {
   this.header = header;
   this.body = body;
   this.type = type;
 }
 
 insert(resultElement: HTMLElement) : void {
 
   // Поскольку тип определен как строковой литерал,
   // включаем его в строку без проблем
   resultElement.innerHTML =
`<div class="${this.type} message" id="message">
 <div class="${this.type} header">${this.header}</div>
 <div class="${this.type} body">${this.body}</div>
</div>`;
 }
}
 
function updateMessage() {
 // Получаем значения элементов в соответствии с типами
 const header = (document.getElementById("header") as HTMLInputElement).value;
 const body = (document.getElementById("body") as HTMLInputElement).value;
 const type = (document.getElementById("type") as HTMLSelectElement).value;
 
 // Настраиваем сообщение
 let msg = new Message(header, body, type as msgTypes);
 
 msg.insert(document.getElementById("result"));
}

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

После компиляции получим такой код:

Файл script.js

var Message = /** @class */ (function () {
   function Message(header, body, type) {
       this.header = header;
       this.body = body;
       this.type = type;
   }
   Message.prototype.insert = function (resultElement) {
       resultElement.innerHTML =
           "<div class=\"".concat(this.type, " message\" id=\"message\">\n  <div class=\"").concat(this.type, " header\">").concat(this.header, "</div>\n  <div class=\"").concat(this.type, " body\">").concat(this.body, "</div>\n</div>");
   };
   return Message;
}());
function updateMessage() {
   // Получаем значение элементов в соответствии с типами
   var header = document.getElementById("header").value;
   var body = document.getElementById("body").value;
   var type = document.getElementById("type").value;
   // Настраиваем сообщение
   var msg = new Message(header, body, type);
   msg.insert(document.getElementById("result"));
}

7. Преимущества и недостатки TypeScript

Преимущества TypeScript составляют, среди прочих, его следующие особенности:

  1. возможность выявлять ошибки еще в редакторе кода и во время компиляции;
  2. поддержка ООП;
  3. удобная система типов;
  4. пригодность для разработки крупных проектов.

Недостатки TypeScript, на наш взгляд, таковы:

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

Итоги

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

В TypeScript реализовано много интересных и полезных возможностей. Поэтому над ним придется помучиться, зато вы освоите язык, популярность которого растет: в 2021 году он обогнал C++ и занял 6-е место среди языков программирования, используемых в работе.

В любом случае знакомство с этим языком идет на пользу, и если вдруг появится возможность работать над крупным фронтенд-проектом на TypeScript, вы будете заранее готовы.

Останні статті

Обучение Power BI – какие онлайн курсы аналитики выбрать

Сегодня мы поговорим о том, как выбрать лучшие курсы Power BI в Украине, особенно для…

13.01.2024

Work.ua назвал самые конкурентные вакансии в IТ за 2023 год

В 2023 году во всех крупнейших регионах конкуренция за вакансию выросла на 5–12%. Не исключением…

08.12.2023

Украинская IT-рекрутерка создала бесплатный трекер поиска работы

Unicorn Hunter/Talent Manager Лина Калиш создала бесплатный трекер поиска работы в Notion, систематизирующий все этапы…

07.12.2023

Mate academy отправит работников в 10-дневный оплачиваемый отпуск

Edtech-стартап Mate academy принял решение отправить своих работников в десятидневный отпуск – с 25 декабря…

07.12.2023

Переписки, фото, история браузера: киевский программист зарабатывал на шпионаже

Служба безопасности Украины задержала в Киеве 46-летнего программиста, который за деньги устанавливал шпионские программы и…

07.12.2023

Как вырасти до сеньйора? Девелопер создал популярную подборку на Github

IT-специалист Джордан Катлер создал и выложил на Github подборку разнообразных ресурсов, которые помогут достичь уровня…

07.12.2023