TypeScript
Привет, TypeScript!
TypeScript это надстройка над JavaScript.
Это значит что TypeScript это язык программирования построенный на базе JavaScript. Это не значит, что это полностью новый язык программирования. Он просто взял инструменты и возможности JavaScript улучшил их и добавил что-то свое новое, как дополнительные что-то свое. Но есть одно очень важное отличие TypeScript от JavaScript!
Код TypeScript не может быть выполнен в окружении где работает JavaScript (например в браузерах). Это значит, что наши привычные рабочие окружения не могут выполнять TypeScript
Что тогда стоит за TypeScript? Получается он лучше, чем JavaScript, но мы его не может нигде выполнять? Ну не совсем. TypeScript - это язык программирования, но он также инструмент! Это очень мощный компилятор, который при работе переводит TypeScript в JavaScript. Поэтому результат который мы получаем когда пишем на TypeScript это JavaScript. Как итог мы можем писать на TypeScript используя очень много полезных инструментов и конструкций и по итогу получим весь наш код переведенный в JavaScript.
В таком случае появляется другой вопрос - зачем его использовать если можно написать код на самом JavaScript? Дело в том, что TypeScript предоставляет удобный синтаксис и более легкий путь к реализации того, что на чистом JavaScript потребовало бы больше усилий и времени. И главное, TypeScript нам дает большую возможность работать с типами данных. Благодаря этому разработчик во время разработки может отловить ошибки ранее чем это бы было при случае написания кода на JavaScript и выполнении его в рабочей среде.
Почему мы используем TypeScript
Вот пример кода:
function add(num1, num2) {
return num1 + num2;
}
console.log(add('2', '3')); // '23'Как результат мы получаем не совсем ожидаемый результат сложения двух чисел, потому что JavaScript проводит конкатенацию, а не вычисление суммы. Эту ошибку мы можем получить выполняя код как есть. Это не техническая ошибка, но мы получаем логическую ошибку в нашем коде. При работе с TypeScript мы можем управлять тем, какой тип данных мы ожидаем при вызове функции и какой тип данных мы должны получить в результате выполнения кода. И еще как дополнительный бонус к этому, мы можем обнаружить эту ошибку еще до момента выполнения кода в рабочей среде, мы отловим на моменте компиляции кода.
Как мы можем решить эту проблему на JavaScript? Добавить проверки на тип данных
function add(num1, num2) {
if (typeof num1 === 'number' && typeof num2 === 'number') {
return num1 + num2;
} else {
return +num1 + +num2;
}
}
console.log(add('2', '3')); // 5Не много ли кода для такой простой функции и отлавливания возможной ошибки? И это при том, что мы пока работали только с маленькой функцией которая складывает два числа. Но если подумать в реальности сколько мы пишем функций и разного кода, то на чистом JavaScript прийдется писать тонны лишнего кода, чтобы убрать все подобные ошибки. Давайте попробуем решить это через TypeScript.
Для этого нам нужно установить TypeScript. https://www.typescriptlang.org/
И не забудьте для работы с TypeScript нужно использовать расширение файла .ts
На TypeScript мы решим эту проблему указав какой тип данных мы ожидаем в аргументах функции
function add(num1: number, num2: number) {
return num1 + num2;
}Давайте посмотрим теперь другой пример этой функции, более реальный
const button = document.querySelector('button');
const input1 = document.getElementById('num1');
const input2 = document.getElementById('num2');
function add(num1, num2) {
return num1 + num2;
}
button.addEventListener('click', () => {
console.log(add(input1.value, input2.value));
});Мы знаем, что значение инпута всегда имеет тип String, а значит наша проблема с логической ошибкой остается, ведь мы передаем в функцию add строки.
Тот же код на TypeScript:
const button = document.querySelector('button');
const input1 = document.getElementById('num1')! as HTMLInputElement;
const input2 = document.getElementById('num2')! as HTMLInputElement;
function add(num1: number, num2: number) {
return num1 + num2;
}
button.addEventListener('click', () => {
console.log(add(input1.value, input2.value));
});Проблема тут заключается в том, что мы все также передаем строки в вызов функции add, хотя она может принимать только числа. Если мы попробуем скомпилировать это тот в JavaScript, то мы получим ошибку
для компиляции запускаем команду tsc <имя файла>
Argument of type 'string' is not assignable to parameter of type 'number'.Что очень круто! Мы получили ошибку на моменте разработки, а не когда код уже выполняется в рабочем окружении.
Давайте исправим это тем, что добавим унарный плюс к передаваемым значениям
const button = document.querySelector('button');
const input1 = document.getElementById('num1')! as HTMLInputElement;
const input2 = document.getElementById('num2')! as HTMLInputElement;
function add(num1: number, num2: number) {
return num1 + num2;
}
button.addEventListener('click', () => {
console.log(add(+input1.value, +input2.value));
});После этого можно попробовать еще раз скомпилировать код, и как результат мы не получим ошибку(уже отлично), а получили мы файл с кодом на JavaScript
var button = document.querySelector('button');
var input1 = document.getElementById('num1');
var input2 = document.getElementById('num2');
function add(num1, num2) {
return num1 + num2;
}
button.addEventListener('click', function () {
console.log(add(+input1.value, +input2.value));
});Мы можем увидеть тут, что наши написанные типы исчезли и мы получили привычный нам чистый JavaScript. Странно подумаете вы, зачем мы так сделали много действий, чтобы получить почти тот же результат. Если вы посмотрите на код TypeScript, вы увидите, что мы использовали дополнительные инструменты, но это просто типы. Когда код компилируется, то компилятор прогоняет ваш код для поиска потенциальных ошибок и если их нет, то на выходе мы получаем обычный JavaScript.
Теперь вы можете использовать код с последнего примера в работе и быть уверенным в его работе. Все это благодаря тому, что мы отловили ошибку написав код на TypeScript и прописав типы. Поэтому написание кода на TypeScript заставляет нас писать лучше, чище и с меньшим количеством ошибок в коде.
Что есть в TypeScript?
Типы
Благодаря им мы можем заранее определить какие данные должны быть и можем предотвратить появление неожиданных и ненужных ошибок. Также используя современные IDE которые имеют встроенную интеграцию с TypeScript мы получаем подсказки о проблемах с типами данных, и еще заранее до компиляции даже обнаружить, что мы написали что-то не так.
Современные возможности JavaScript (компилируются в код который понимают старые браузеры)
Не менее сильная возможность TypeScript использовать самые новые возможности JavaScript, не задумывая о совместимости и поддержке. TypeScript компилирует код в чистый JavaScript, который работает даже в старых браузерах. Можно сказать он работает как Babel, для JavaScript. Только тут это уже встроенно в TypeScript!
Новые инструменты которых нет в JavaScript (к примеру интерфейсы и дженерики)
TypeScript имеет много своих отдельных инструментов, которых нет в чистом JavaScript. Они позволяют нам писать еще лучше, находить пути как избежать еще ошибки, и дают больше контроля над тем что мы пишем и как оно будет работать
Инструменты из общего программирования такие как декораторы
В нем присутствуют инструменты, которые относятся к общему программированию вне зависимости от языка программирования.
Очень гибкая возможность настройки работы TypeScript
Поддержка TypeScript всеми современными IDE
Даже если вы пишете на JavaScript, то IDE у которой есть интеграция с TypeScript может вам подсказать, что есть где-то ошибка и вы где-то вызываете метод у undefined к примеру.
Все эти возможности TypeScript и привлекают нас использовать его все больше, чем и обусловлен рост его популярности в последние несколько лет
Types
JavaScript предоставляет набор типов данных, но TypeScript имеет еще дополнительные к общему набору и также позволяет создавать свои типы.
Начнем мы с того, что ознакомимся с общим набором которые есть в обоих этих языках, посмотрим на них и сравним как тот или другой язык работает с ними.
Number
Как в JavaScript, так и в TypeScript нет специальных типов для целочисленных чисел или с плавающей точкой. Все числа имеют один тип number.
String
Как в JavaScript, так и в TypeScript мы можем создавать строки, используя, три варианта кавычек.
Boolean
Как в JavaScript, так и в TypeScript мы можем работать с булевыми значения true и false.
Пока остановимся на них и посмотрим как с ними можно работать. Посмотрим на уже ранее созданный пример кода
function add(num1, num2) {
return num1 + num2;
}
const a = '5';
const b = 3.5;
console.log(add(a, b));Все сработает хорошо, хотя мы и написали TypeScript код, но мы все таки получаем результат не совсем логичный. Это все потому что если мы в TypeScript не используем типы или другие его конструкции, то он ведет себя как JavaScript. Поэтому я вам говорил, что если вы знаете JavaScript, то знаете уже частично и TypeScript.
Теперь если мы добавим типы для аргументов функции, то мы получим ошибку при компиляции
function add(num1: number, num2: number) {
return num1 + num2;
}
const a = '5';
const b = 3.5;
console.log(add(a, b));
// Argument of type '"5"' is not assignable to parameter of type 'number'.Поэтому хочу еще раз повториться, что TypeScript помогает нам только при компиляции и разработке, он бессилен во время выполнения кода, потому что выполняется уже JavaScript код.
В JavaScript мы имеем динамическую типизацию. Это значит, что тип переменной определяет в момент выполнения код. В TypeScript же мы имеем статическую типизацию, это значит что тип переменной определяется до выполнения кода, еще в момент разработки и этот тип не меняется и во время выполнения кода. Это нам может помочь тем, что если мы обозначили, что переменная может содержать число, и в ходе написания кода мы назначаем переменой значение строки, то TypeScript нам об этом сообщит и выдаст ошибку. А значит нам не нужно писать что-то наподобие typeof num1 === ‘number’ && typeof num2 === ‘number’, чтобы обезопасить себя от ошибок, за нас это делает TypeScript.
Теперь добавим еще boolean:
function add(num1: number, num2: number, showResult: boolean) {
if (showResult) {
console.log(num1 + num2);
} else {
return num1 + num2;
}
}
const a = 5;
const b = 3.5;
const printResult = true;
add(a, b, printResult);и string
function add(num1: number, num2: number, showResult: boolean, phrase: string) {
const result = num1 + num2;
if (showResult) {
console.log(phrase + result);
} else {
return result;
}
}
const a = 5;
const b = 3.5;
const printResult = true;
const resultPhrase = 'Result is: ';
add(a, b, printResult, resultPhrase);Как можете заметить ничего сложного в работе с такими типами данных как число, строка или булевое значение нет. Просто нужно указать его тип. Теперь поговорим о том, КАК указываются типы.
Указание типов
В TypeScript есть специальные ключевые слова для указания типов, такие как number, string, boolean (с ними мы только что познакомились). И есть специальные механизм для указания типов - мы должны поставить двоеточие перед типом. Посмотрите как мы делали это в аргументах функции. Этот синтаксис часть TypeScript, мы его не увидим в итоговом коде который получим после компиляции.
Но почему мы не писали подобное указание типов для переменных которые мы создавали?
//...
const result = num1 + num2;
//...
const a = 5;
const b = 3.5;
const printResult = true;
const resultPhrase = 'Result is: ';А все потом что TypeScript есть встроенный механизм, который называется type inference. Это значит, что TypeScript делает все возможное сам, чтобы понять что за тип находится в переменной. В данном примере он понял сам, что у переменной a тип число, потому что мы назначили число в нее при инициализации. Далее TypeScript уже работает с этой переменной зная ее тип. И это не зависит от того как мы определили переменную через const или let.
Но если мы не присваивали значение переменной в момент инициализации? Тут есть нюанс
// 1
let result: number;
result = 5;
result = 'hello'; // Type 'string' is not assignable to type 'number'
// 2
let result2;
result2 = 5;
result2 = 'hello';В первом варианте мы указываем тип при создании переменной и далее TypeScript позволяет нам присваивать значение в эту переменную только те которые имеют тип “число”. Из-за этого далее когда мы хотим присвоить строку в переменную, мы получаем ошибку.
Во втором случае ошибку мы не получаем из-за того, что тип переменой не определен и TypeScript не понимает какой тип значения может принимать эта переменная.
Как вывод можно сказать, что TypeScript определяет тип переменной в момент ее инициализации и то как она инициализирована.
Теперь мы знаем как определяется тип переменных при работе с такими типами данных как Number, String, Boolean. Теперь пойдем дальше и посмотрим на объекты.
Object
Как в JavaScript, так и в TypeScript мы можем работать с объектами в привычном виде { key: value };
const person = {
name: "John",
age: 30
};
console.log(person);Этот код отработает как нужно. Мы получим в консоле отображение нашего объекта. Вопрос, а что будет, если мы обратимся к ключу которого нет?
const person = {
name: "John",
age: 30
};
console.log(person.job);В чистом JavaScript мы не получим ошибку, нам просто выдадут undefined. В TypeScript мы получим ошибку
Property 'job' does not exist on type '{ name: string; age: number; }'.Это потому что TypeScript, как и в ситуации с инициализацией переменной, автоматически определяет тип переменной согласно присвоенного ей значения. Но что это за тип такой { name: string; age: number; }?
При работе с объектами TypeScript сам работает с таким типом в котором должны быть определены ключи и тип данных которые они могут содержать и нас заставляет следовать этому. Мы должны полностью описывать какой объект будет, и что он будет содержать.
В данном случае TypeScript сам определил тип, но мы и сами могли бы его указать, выглядело бы это так:
const person: {
name: string;
age: number;
} = {
name: "John",
age: 30
};Если объект содержит в себе другой объект, то ничего сложного тут нет, просто нужно описать тип внутреннего объекта
const person: {
name: string;
age: number;
job: {
position: string;
salary: number;
}
} = {
name: "John",
age: 30,
job: {
position: 'developer',
salary: 2000
}
};Array
Как в JavaScript, так и в TypeScript мы можем работать с объектами в привычном виде [];
Для работы с массивом давайте дополним объект person ключом hobbies
const person = {
name: "John",
age: 30,
hobbies: ['Driving', 'Cooking']
};В данном случае тип ключа hobbies выглядит так string[ ]. TypeScript определяет, что находится в массиве и на этой основе указывает тип. Для обозначения, что это набор значений с таким типом он добавляет cкобки [ ]. Также это работает и для массива чисел number[ ] и для булевых boolean[ ] и для объектов { name: string; age: number; }[ ]. Как и в ситуации с другими типами, мы можем сами указать что за тип данных будет храниться в переменной, указав по соответствующему принципу.
Но ведь массив может содержать разные значения, разных типом. В реальности такое делают редко, но в этому случае нам может помочь специальный тип any, о нем мы поговорим позже.
Также хочу отметить, что TypeScript работает с массивом глубоко, это значит он понимает, что за элементы в нем хранятся и будет применять к ним соответствующие проверки.