Как не надо учить TypeScript
Ошибка 1: Игнорирование JavaScript
Содержание статьи:
TypeScript — это надмножество JavaScript, и с тех пор его так и рекламируют. Это означает, что JavaScript в значительной степени является частью языка. Все это так. Выбор TypeScript не дает вам возможности отказаться от JavaScript и его неустойчивого поведения. Но TypeScript упрощает его понимание. И вы можете видеть, как JavaScript прорывается повсюду.
Было бы очень хорошо обрабатывать ошибки так, как вы привыкли в других языках программирования:
JavaScript try { // something with Axios, for example } catch(e: AxiosError) { // ^^^^^^^^^^ Error 1196 }
12345 | try { // something with Axios, for example} catch(e: AxiosError) {// ^^^^^^^^^^ Error 1196} |
JavaScript. Быстрый старт
Изучите основы JavaScript на практическом примере по созданию веб-приложения
Но это невозможно. И причина в том, как работают ошибки JavaScript. Код, который имел бы смысл в TypeScript, но не выполним в JavaScript.
Другой пример: использование Object.keys и ожидание простого доступа к свойствам также может вызвать проблемы.
JavaScript 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]) })
123456789 | 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 определять типы за вас. Почему? Позвольте мне объяснить, что такое аннотация типа.
Аннотация типа — это способ указать, какие контракты должны быть проверены. Если вы добавляете аннотацию типа к объявлению переменной, вы указываете компилятору проверять соответствие типов во время присваивания.
JavaScript type Person = { name: string, age: number } const me: Person = createPerson()
123456 | type Person = { name: string, age: number} const me: Person = createPerson() |
Если createPerson возвращает что-то, что несовместимо с Person, TypeScript выдаст ошибку. Используйте это, если вы действительно хотите быть уверены, что имеете дело с правильным типом.
Кроме того, с этого момента me имеет тип Person, и TypeScript будет рассматривать его как файл Person. Если в me, например, есть еще свойство profession, TypeScript не позволит вам получить к нему доступ, ибо оно не определено в Person.
Если вы добавляете аннотацию типа к возвращаемому значению сигнатуры функции, вы указываете компилятору проверять, соответствуют ли типы в тот момент, когда вы возвращаете это значение.
JavaScript function createPerson(): Person { return { name: «Stefan», age: 39 } }
123 | function createPerson(): Person { return { name: «Stefan», age: 39 }} |
Если я верну что-то, что не соответствует Person, TypeScript выдаст ошибку. Используйте это, если вы хотите быть полностью уверены, что возвращаете правильный тип. Это особенно удобно, если вы работаете с функциями, которые создают большие объекты из различных источников.
Если вы добавляете аннотацию типа к параметрам сигнатуры функции, вы указываете компилятору проверять соответствие типов в тот момент, когда вы передаете аргументы.
JavaScript function printPerson(person: Person) { console.log(person.name, person.age) } printPerson(me)
12345 | function printPerson(person: Person) { console.log(person.name, person.age)} printPerson(me) |
На мой взгляд, это самая важная и неизбежная аннотация типа. Все остальное можно предположить.
JavaScript 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)
12345678910111213141516171819202122 | 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 compatiblefunction printPerson(person: Person) { console.log(person.name, person.age)} // All worksprintPerson(me) |
Всегда используйте аннотации типов с параметрами функций. Тут вы должны проверить соответствие типу. Это не только намного удобнее, но и дает массу преимуществ. Вы получаете, например, полиморфизм бесплатно.
JavaScript 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)
12345678910111213141516171819202122232425262728293031323334353637 | 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. Например:
Фреймворк VUE JS: быстрый старт, первые результаты
Получите бесплатный курс и узнайте, как создать веб-приложение на трендовой Frontend-технологии VUE JS с полного нуля
JavaScript // 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) }
123456789 | // Collection is in TypeScript land! —> typetype Collection<T> = { entries: T[]} // printCollection is in JavaScript land! —> valuefunction printCollection(coll: Collection<unknown>) { console.log(…coll.entries)} |
Поскольку слой типов находится поверх слоя значений, можно использовать значения в слое типов, но не наоборот. У нас также есть явные ключевые слова для этого.
JavaScript // a value const person = { name: «Stefan» } // a type type Person = typeof person;
1234567 | // a valueconst person = { name: «Stefan»} // a typetype Person = typeof person; |
Typeof создает имя, доступное в слое типов, из слоя значений ниже. Раздражает, когда есть декларация типов, которые создают как типы, так и значения. Например, классы можно использовать в слое TypeScript как тип, а в JavaScript — как значение.
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) { //… }
123456789101112131415161718 | // declarationclass Person { name: string constructor(n: string) { this.name = n }} // valueconst person = new Person(«Stefan») // typetype PersonCollection = Collection<Person> function printPersons(coll: PersonCollection) { //…} |
Обычно мы определяем классы, типы, интерфейсы, перечисления и т. д. с заглавной буквы. И даже если они могут присваивать значения, они точно объявляют типы. Ну, по крайней мере, до тех пор, пока вы не напишете функции в верхнем регистре для своего приложения React.
Если вы привыкли использовать имена в качестве типов и значений, вы очень удивитесь, если вдруг получите ошибку TS2749: ‘YourType’ ссылается на значение, но используется как тип.
JavaScript 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 }
12345678910111213141516 | 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, вероятно, будет хорошей идеей сосредоточиться на функциях, переменных и простых псевдонимах типов (или интерфейсах, если вам это больше нравится). Это должно дать вам хорошее представление о том, что происходит на уровне типов и что происходит на уровне значений.
Ошибка 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
Источник: webformyself.com