Главная » Статьи » Как не надо учить TypeScript

Как не надо учить TypeScript

От автора: «Мы с TypeScript никогда не станем друзьями». Как часто я слышал эту фразу? Изучение TypeScript даже в 2022 году может показаться разочарованием. И по очень разным причинам. Люди, которые пишут на Java или C# обнаруживают, что многие вещи работают не так, как должны. Даже люди, которые большую часть своего времени работали с JavaScript, получают ошибки компилятора. Вот несколько ошибок, которые я видел, когда люди начинали работать с TypeScript. Я надеюсь, что они будут полезны для вас!

Ошибка 1: Игнорирование JavaScript

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

Было бы очень хорошо обрабатывать ошибки так, как вы привыкли в других языках программирования:

try { // something with Axios, for example
} catch(e: AxiosError) {
// ^^^^^^^^^^ Error 1196
}

Но это невозможно. И причина в том, как работают ошибки JavaScript. Код, который имел бы смысл в TypeScript, но не выполним в JavaScript.

Другой пример: использование Object.keys и ожидание простого доступа к свойствам также может вызвать проблемы.

type Person = { name: string, age: number, id: number,
}
declare const me: Person; Object.keys(me).forEach(key => { // the next line throws red squigglies at us console.log(me[key])
})

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

Если вы изучаете TypeScript без какого-либо опыта работы с JavaScript, учитесь различать JavaScript и систему типов. Кроме того, научитесь искать правильные вещи. Именованные параметры в функциях. Вы можете сделать это с объектами в качестве аргументов. Хороший паттерн. Однако это часть JavaScript. Условная последовательность? Сначала она реализована в компиляторе TypeScript, но это также функция JavaScript. Классы и расширение классов? JavaScript. Приватные поля класса? Также JavaScript.

Программный код, который действительно что-то делает, большей частью относится к JavaScript. Недавно на веб-сайте TypeScript появилось гораздо более четкое заявление о том, что значит использовать TypeScript: TypeScript — это JavaScript с синтаксисом для типов. Здесь вся суть! TypeScript — это JavaScript. Понимание JavaScript является ключом к пониманию TypeScript.

Ошибка 2: Аннотирование всего

Аннотация типа — это способ явно указать, какие типы следует ожидать. Вам знакомы случаи, которые очень характерны другим языкам программирования, где многословие StringBuilder stringBuilder = new StringBuilder() гарантирует, что вы действительно имеете дело с типом StringBuilder. Противоположным является вывод типа, когда TypeScript пытается определить тип для вас. let a_number = 2 имеет тип number.

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

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

type Person = { name: string, age: number
} const me: Person = createPerson()

Если createPerson возвращает что-то, что несовместимо с Person, TypeScript выдаст ошибку. Используйте это, если вы действительно хотите быть уверены, что имеете дело с правильным типом.

Кроме того, с этого момента me имеет тип Person, и TypeScript будет рассматривать его как файл Person. Если в me, например, есть еще свойство profession, TypeScript не позволит вам получить к нему доступ, ибо оно не определено в Person.

Если вы добавляете аннотацию типа к возвращаемому значению сигнатуры функции, вы указываете компилятору проверять, соответствуют ли типы в тот момент, когда вы возвращаете это значение.

function createPerson(): Person { return { name: "Stefan", age: 39 }
}

Если я верну что-то, что не соответствует Person, TypeScript выдаст ошибку. Используйте это, если вы хотите быть полностью уверены, что возвращаете правильный тип. Это особенно удобно, если вы работаете с функциями, которые создают большие объекты из различных источников.

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

function printPerson(person: Person) { console.log(person.name, person.age)
} printPerson(me)

На мой взгляд, это самая важная и неизбежная аннотация типа. Все остальное можно предположить.

type Person = { name: string, age: number
} // Inferred!
// return type is { name: string, age: number }
function createPerson() { return { name: "Stefan", age: 39}
} // Inferred!
// me is type of { name: string, age: number}
const me = createPerson() // Annotated! You have to check if types are compatible
function printPerson(person: Person) { console.log(person.name, person.age)
} // All works
printPerson(me)

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

type Person = { name: string, age: number
} type Studying = { semester: number
} type Student = { id: string, age: number, semester: number
} function createPerson() { return { name: "Stefan", age: 39, semester: 25, id: "XPA"}
} function printPerson(person: Person) { console.log(person.name, person.age)
} function studyForAnotherSemester(student: Studying) { student.semester++
} function isLongTimeStudent(student: Student) { return student.age - student.semester / 2 > 30 && student.semester > 20
} const me = createPerson() // All work!
printPerson(me)
studyForAnotherSemester(me)
isLongTimeStudent(me)

Student, Person и Studying имеют некоторое сходство, но не связаны друг с другом. createPerson возвращает то, что совместимо со всеми тремя типами. Если бы мы аннотировали слишком много, нам пришлось бы создавать гораздо больше типов и гораздо больше проверок, чем необходимо, без какой-либо пользы.

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

Ошибка 3: Ошибки типов значений

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

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

TypeScript, являющийся неким слоем поверх JavaScript, также означает, что разный синтаксис влияет на разные уровни. В то время как function или const создает имя в JavaScript, объявления type или interface способствует именованию в TypeScript. Например:

// Collection is in TypeScript land! --> type
type Collection<T> = { entries: T[]
} // printCollection is in JavaScript land! --> value
function printCollection(coll: Collection<unknown>) { console.log(...coll.entries)
}

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

// a value
const person = { name: "Stefan"
} // a type
type Person = typeof person;

Typeof создает имя, доступное в слое типов, из слоя значений ниже. Раздражает, когда есть декларация типов, которые создают как типы, так и значения. Например, классы можно использовать в слое TypeScript как тип, а в JavaScript — как значение.

// declaration
class Person { name: string constructor(n: string) { this.name = n }
} // value
const person = new Person("Stefan") // type
type PersonCollection = Collection<Person> function printPersons(coll: PersonCollection) { //...
}

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

Если вы привыкли использовать имена в качестве типов и значений, вы очень удивитесь, если вдруг получите ошибку TS2749: ‘YourType’ ссылается на значение, но используется как тип.

type PersonProps = { name: string
} function Person({ name }: PersonProps) { return <p>{name}</p>
} type Collection<T> = { entries: T
} type PrintComponentProps = { collection: Collection<Person> // ERROR! // 'Person' refers to a value, but is being used as a type
}

Вот где TypeScript может стать действительно запутанным. Что такое тип, что такое значение, зачем нужно их разделять, почему это не работает, а как в других языках программирования? Внезапно вы видите, что сталкиваетесь с вызовами typeof или даже с вспомогательным типом InstanceType, потому что понимаете, что классы на самом деле предоставляют два типа.

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

Как не надо учить TypeScript

При изучении TypeScript, вероятно, будет хорошей идеей сосредоточиться на функциях, переменных и простых псевдонимах типов (или интерфейсах, если вам это больше нравится). Это должно дать вам хорошее представление о том, что происходит на уровне типов и что происходит на уровне значений.

Ошибка 4: Идти ва-банк в начале

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

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

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

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

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

Возьмите части своего приложения и перенесите их на TypeScript вместо того, чтобы переносить все. TypeScript совместим с JavaScript (allowJS)

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

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

Не дойтесь использования any. Вопреки распространенному мнению, использование any абсолютно допустимо, если any используется явным образом.

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

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

Ошибка 5: Изучение неправильного TypeScript

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

namespace

declare

module

<reference>

abstract

unique

Это не означает, что эти ключевые слова не вносят важного вклада для различных вариантов использования. Однако при изучении TypeScript вам не следует работать с ними в начале. И это все!

Автор: Stefan Baumgartner

Источник: fettblog.eu

Редакция: Команда webformyself.

Читайте нас в Telegram, VK, Яндекс.Дзен