TypeScript для программистов Java/C#
TypeScript — популярный выбор для программистов, привыкших к другим языкам со статической типизацией, таким как C# и Java.
Система типов TypeScript предлагает многие из тех же преимуществ, такие как лучшая автодополнение кода, более раннее обнаружение ошибок и более чёткое взаимодействие между частями вашей программы. Хотя TypeScript предоставляет множество знакомых функций для этих разработчиков, стоит отступить, чтобы увидеть, чем JavaScript (и, следовательно, TypeScript) отличается от традиционных языков ООП. Понимание этих различий поможет вам писать лучший код JavaScript и избежать распространённых ошибок, в которые могут попасть программисты, переходящие напрямую с C#/Java на TypeScript.
Изучение JavaScript
Если вы уже знакомы с JavaScript, но в основном являетесь программистом Java или C#, эта вводная страница поможет объяснить некоторые распространённые заблуждения и ловушки, на которые вы можете попасть. Некоторые способы моделирования типов в TypeScript значительно отличаются от Java или C#, и важно помнить об этом при изучении TypeScript.
Если вы являетесь программистом Java или C#, который новичок в JavaScript вообще, мы рекомендуем сначала немного изучить JavaScript без типов, чтобы понять поведение JavaScript во время выполнения. Поскольку TypeScript не изменяет способ выполнения вашего кода, вам всё равно придётся узнать, как работает JavaScript, чтобы написать код, который действительно что-то делает!
Важно помнить, что TypeScript использует тот же режим выполнения, что и JavaScript, поэтому любые ресурсы о том, как выполнить определённое поведение во время выполнения (преобразование строки в число, отображение всплывающего сообщения, запись файла в диск и т. д.), всегда будут одинаково хорошо применяться к программам TypeScript. Не ограничивайте себя только ресурсами, специфичными для TypeScript!
Переосмысление класса
C# и Java — это то, что мы можем назвать языками обязательного ООП. В этих языках класс является основной единицей организации кода, а также основной контейнер всех данных и поведения во время выполнения. Принуждение к размещению всей функциональности и данных в классах может быть хорошей моделью предметной области для некоторых задач, но не каждая предметная область нуждается в представлении таким способом.
Свободные функции и данные
В JavaScript функции могут находиться где угодно, а данные могут свободно передаваться без необходимости размещения внутри предварительно определённого class или struct. Эта гибкость чрезвычайно мощная. «Свободные» функции (те, которые не связаны с классом), работающие с данными без предполагаемой иерархии ООП, обычно являются предпочтительной моделью для написания программ на JavaScript.
Статические классы
Кроме того, некоторые конструкции из C# и Java, такие как синглтоны и статические классы, не нужны в TypeScript.
ООП в TypeScript
Тем не менее, вы по-прежнему можете использовать классы, если хотите! Некоторые задачи подходят для решения с помощью традиционной иерархии ООП, и поддержка TypeScript для классов JavaScript сделает эти модели ещё более мощными. TypeScript поддерживает многие распространённые шаблоны, такие как реализация интерфейсов, наследование и статические методы.
Мы рассмотрим классы позже в этом руководстве.
Переосмысление типов
Понимание типа в TypeScript на самом деле сильно отличается от C# или Java. Давайте рассмотрим некоторые различия.
Номинальные реифицированные системы типов
В C# или Java любое данное значение или объект имеет один точный тип — либо null, примитивный или известный тип класса. Мы можем вызывать методы, такие как value.GetType() или value.getClass() для запроса точного типа во время выполнения. Определение этого типа будет находиться в классе где-то с определённым именем, и мы не можем использовать два класса с похожими формами вместо друг друга, если между ними нет явного отношения наследования или общереализованного интерфейса.
Эти аспекты описывают реифицированную, номинальную систему типов. Типы, которые мы написали в коде, присутствуют во время выполнения, и типы связаны через их объявления, а не через их структуру.
Типы как множества
В C# или Java имеет смысл рассматривать взаимно однозначное соответствие между типами во время выполнения и их объявлениями на этапе компиляции.
В TypeScript лучше рассматривать тип как множество значений, имеющих нечто общее. Поскольку типы — это просто множества, определённое значение может принадлежать многим множествам одновременно.
Как только вы начнете рассматривать типы как множества, некоторые операции станут очень естественными. Например, в C# неудобно передавать значение, которое либо string или int, потому что нет одного типа, который представляет этот вид значения.
В TypeScript это становится очень естественным, как только вы понимаете, что каждый тип — это просто множество. Как описать значение, которое принадлежит либо множеству string или множеству number? Оно просто принадлежит объединению этих множеств: string | number.
TypeScript предоставляет ряд механизмов для работы с типами в теории множеств, и вы найдете их более интуитивными, если будете рассматривать типы как множества.
Стираемые структурные типы
В TypeScript объекты не имеют единственного точного типа. Например, если мы создаём объект, удовлетворяющий интерфейсу, мы можем использовать этот объект там, где ожидается этот интерфейс, даже если между ними не было декларативного отношения.
interface Pointlike {
x: number;
y: number;
}
interface Named {
name: string;
}
function logPoint(point: Pointlike) {
console.log("x = " + point.x + ", y = " + point.y);
}
function logName(x: Named) {
console.log("Hello, " + x.name);
}
const obj = {
x: 0,
y: 0,
name: "Origin",
};
logPoint(obj);
logName(obj); Система типов TypeScript является структурной, а не номинальной: мы можем использовать obj как Pointlike, потому что у него есть свойства x и y , которые оба являются числами. Связи между типами определяются содержащимися в них свойствами, а не тем, объявлялись ли они с каким-то определённым отношением.
Система типов TypeScript также является не реифицированной: нет ничего во время выполнения, что скажет нам, что obj является Pointlike. Фактически, тип Pointlike не присутствует ни в какой форме во время выполнения.
Возвращаясь к идее типов как множеств, мы можем рассматривать obj как член как множества Pointlike значений, так и множества Named значений.
Последствия структурной типизации
Пустые типы
Первый заключается в том, что пустой тип, кажется, противоречит ожиданиям:
class Empty {}
function fn(arg: Empty) {
// do something?
}
// No error, but this isn't an 'Empty' ?
fn({ k: 10 }); TypeScript определяет, является ли вызов fn здесь допустимым, посмотрев, является ли предоставленный аргумент допустимым Empty. Он делает это, изучая структуру { k: 10 } и class Empty { }. Мы видим, что { k: 10 } имеет все свойства, которые есть у Empty, потому что у Empty нет свойств. Следовательно, это допустимый вызов!
Это может показаться неожиданным, но в конечном счёте это очень похоже на отношение, навязанное в номинальных языках ООП. Подкласс не может удалить свойство своего базового класса, поскольку это разрушило бы естественное отношение подтипов между производным классом и его базовым классом. Структурные системы типов просто неявно идентифицируют это отношение, описывая подтипы в терминах наличия свойств совместимых типов.
Идентичные типы
Ещё одним частым источником удивления являются идентичные типы:
class Car {
drive() {
// hit the gas
}
}
class Golfer {
drive() {
// hit the ball far
}
}
// No error?
let w: Car = new Golfer(); Это не ошибка, поскольку структуры этих классов одинаковы. Хотя это может показаться потенциальным источником путаницы, на практике идентичные классы, которые не должны быть связаны, не являются распространёнными.
Подробнее о взаимоотношениях классов друг с другом вы узнаете в главе «Классы».
Рефлексия
Программисты ООП привыкли к возможности запроса типа любого значения, даже универсального:
// C#
static void LogType<T>() {
Console.WriteLine(typeof(T).Name);
} Поскольку система типов TypeScript полностью стирается, информация о, например, создании параметра универсального типа не доступна во время выполнения.
JavaScript имеет некоторые ограниченные примитивы, такие как typeof и instanceof, но помните, что эти операторы всё ещё работают со значениями, как они существуют в коде с удалёнными типами. Например, typeof (new Car()) будет "object", а не Car или "Car".
Следующие шаги
Это был краткий обзор синтаксиса и инструментов, используемых в повседневном TypeScript. Отсюда вы можете:
- Прочитать полное руководство от начала до конца
- Изучить примеры Playground
© 2012-2023 Microsoft
Licensed under the Apache License, Version 2.0.
https://www.typescriptlang.org/docs/handbook/typescript-in-5-minutes-oop.html