Добрый день, меня зовут Павел Поляков, я Principal Engineer в каршеринг компании SHARE NOW, в Гамбурге в ???????? Германии. А еще я автор телеграм канала Хороший разработчик знает, где рассказываю обо всем, что обычно знает хороший разработчик.

Сегодня хочу поговорить про то как НЕ надо учить TypeScript. Какие ошибки чаще всего делают новички и почему TypeScript может так раздражать? Это перевод оригинальной статьи.

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

“TypeScript и я никогда не будут друзьями!”. Ох, как часто я слышал эту фразу? Учить TypeScript даже в 2022 году может быть сложно. И сложно может быть по множеству разных причин. Люди, которые пишут на Java или C# могут обнаружить вещи, которые работают не так, как должны. Люди, которые программировали на JavaScript большую часть своей жизни пугаются, когда на них кричит компилятор. Давайте рассмотрим некоторые ошибки, которые совершают люди, когда начинают знакомство с TypeScript. Надеюсь, они будут вам полезны!

Эта статья была вдохновлена статьей Denys “Как не надо учить Rust”, которую я тоже рекомендую прочитать.

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

TypeScript это суперсет JavaScript, он продается под таким соусом с самого начала. Это значит, что JavaScript является частью TypeScript. Весь TypeScript состоит из JavaScript. Если вы выбрали TypeScript, то это не дает вам права выкинуть JavaScript и его специфическое поведение. Но TypeScript помогает понять почему JavaScript так себя ведет. Вы увидите, как 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: TypeScript это JavaScript с синтаксисом для типов. Вот оно, видели? TypeScript это и есть JavaScript. Понимание JavaScript является ключом к пониманию TypeScript.

Ошибка 2: аннотации везде

Аннотация типа это способ в явном виде указать какой тип стоит ожидать. Понимаете? Тот самый случай, когда в других языках, когда вы очень подробно пишитеStringBuilder stringBuilder = new StringBuilder() и понимаете, что это точно что-то, что имеет тип StringBuilder. Противоположностью является интерфейс типа, когда TypeScript пытается понять что это за тип за вас. let 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)

На мой взгляд это наиболее важный и неизбежный случай, когда надо применять аннотации типа. А вот во всех остальных случаях можно положиться на TypeScript, он сам определит тип (inferred type).

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 и не перебарщиваете с аннотациями типов, то вам легче работать с системой в которую интегрирована типизация (в отличие от JavaScript).

Ошибка 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) {
  //...
}

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

Если вы привыкли называть значения и типы используя одинаковый стиль, вы можете внезапно словить ошибку TS2749: ‘YourType’ refers to a value, but is being used as a type.

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 документации, это неплохо резюмировано:

Определение

Тип

Значение

Class

Enum

Interface

Type Alias

Function

Variable

Когда вы учите TypeScript хорошей идеей будет сосредоточиться на функциях, переменных и простых псевдонимах типов (type aliases). Ну или интерфейсах, если вы больше любите их. Это даст вам хорошее представление о том, что происходит в слое, который отвечает за типы, и что происходит в слое, который отвечает за значения.

Ошибка 4: Поставить все на TypeScript с самого начала

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

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

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

Мы все это испытывали, не правда ли?

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

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

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

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

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

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

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

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

Ошибка 5: Учить не тот TypeScript

Опять, этот пункт вдохновлен статьей “Как не надо учить Rust”. Если вашему коду нужны конструкции из списка ниже, вы, скорее всего, делаете что-то не то с TypeScript. Либо уже так продвинулись, что вам не нужно читать эту статью. Вот список:

  • namespace

  • declare

  • module

  • <reference>

  • abstract

  • unique

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

Вот и все. Мне интересно как вы учили TypeScript и какие преграды встречали на своем пути. Вы знаете еще какие-то ошибки, которые обычно совершают во время изучения TypeScript? Пишите в комментарии!

А еще...

Здесь говорю опять я, Павел. В конце еще раз приглашу вас в свой Telegram-канал. На канале Хороший разработчик знает я минимум три раза в неделю простым языком рассказываю про свой опыт, хард скиллы и софт скиллы. Я 15+ лет в IT, мне есть чем поделиться. Все это нужно разработчику, чтобы делать свою работу хорошо, работать в удовольствие, быть востребованным на рынке и получать высокую компенсацию.

А для любителей картинок и историй есть ???? Instagram.

Спасибо ????

Комментарии (33)


  1. Alexandroppolus
    21.01.2022 11:58
    +7

    Аннотация возвращаемого значения функции - спорный холиварный поинт. Лично мне с ней удобнее: тайпчек делается на месте, в описании функции. Иначе проверка переезжает в места использования функции, и вместо одной TS-ошибки выскочит 99 ошибок в разных файлах.

    Реактовские компоненты лучше объявлять так:

    const Person: React.FC<PersonProps> = (props) => ...

    Здесь сразу проверка и аргументов, и возвращаемого значения. Ну и другие плюшки.

    И не возникает вопросов, как делать пример с коллекцией:

    collection: Collection<React.FC<PersonProps>>


    1. warhamster
      21.01.2022 12:11
      +2

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


    1. bohdan-shulha
      21.01.2022 13:50
      +1

      Реактовские компоненты лучше объявлять так:

      Когда у вас компонент обрабатывает children и вот так, если нет:

      const Person: React.VFC = (props) => ...

      Вся соль здесь:

      type FC<P = {}> = FunctionComponent<P>;
      
      interface FunctionComponent<P = {}> {
        // Вот тут - PropsWithChildren
        (props: PropsWithChildren<P>, context?: any): ReactElement<any, any> | null
        ...
      }
      
      type VFC<P = {}> = VoidFunctionComponent<P>;
      
      interface VoidFunctionComponent<P = {}> {
        // А тут - только те props, которые вы указали
        (props: P, context?: any): ReactElement<any, any> | null
        ...
      ]

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


    1. Ostic
      21.01.2022 20:14
      -1

      если ничего не путаю, вроде как не рекомендуется использовать React.FC


    1. ogregor
      23.01.2022 00:16

      А можно просто флаг поставить. Stict=false


  1. dust
    21.01.2022 13:38
    +2

    Другая большая штука, справедливо не описанная в статье из-за сравнительной сложности, это Generics. Для тех кто пришел из PHP или JS, это может быть немного непонятно с первого взгляда, но вещь очень мощная. Простой пример: функция враппер для обобщенных ajax вызовов:

    async function fetchApi(path: string) {
      const response = await fetch(`https://example.com/api${path}`)
      return response.json();
    }

    Функция выше может возвращать разные типы данных, в зависимости от path. Как раз для таких случаев можно смело переходить на Generic:

    type User = {
      name: string;
    }
    async function fetchApi<ResultType>(path: string): Promise<ResultType> {
      const response = await fetch(`https://example.com/api${path}`);
      return response.json();
    }
    const data = await fetchApi<User[]>('/users')

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


    1. Metotron0
      21.01.2022 14:25

      Пришёл из PHP и JS, примерно понял, как работают обобщённые типы, но не понимаю, как писать, чтобы они были правильные. Это как с английским: читать могу, но когда сам пишу, то даже не могу правильно выбрать между can, could и would.

      Кроме того, я путаюсь, где объявление типа, а где уже его использование. В вашем примере я начал искать, где описан тип, в который подставляется ResultType, потому что привык, что в учебниках сперва опишут в терминах T и P, а потом подставляют, чтобы вышло что-то типа Promise<number>, а сам Promise<T> лежит где-то ещё. В общем, очень сложно уложить всё в голове. А a<b<c>> я вообще пропускаю, не пытаясь понять, просто верю, что эта магия работает, сам я такое не напишу. Мне сама концепция не понятна, как понимать эти угловые скобки. Может быть, это как вызов функции с параметром, и тогда b<c> как бы возвращает некоторый тип, который идёт параметром в a<>. Или всё хуже?

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


      1. dust
        21.01.2022 14:50

        Все проще! Для себя я это объясняю так: в угловых скобках ПРИ ВЫЗОВЕ указывается тип данных с которым функция будет работать. А в ОБЪЯВЛЕНИИ функции в угловых скобках стоит Generic, обобщенный тип, который принимает тип из ВЫЗОВА. Более сложные конструкции a<b<c>>() – просто описывают "как бы вложенные объекты". a – вернет объект типа b<c>, который вернет объект типа c.
        Еще раз, как верно подмечено в статье, TypeScript не делает работу, он служит описанием взаимодейстия типов в вашем коде.


        1. dust
          21.01.2022 14:56

          Хотя и это не совсем правда, TS может много, но это уже совсем другая история https://github.com/type-challenges/type-challenges


        1. Metotron0
          21.01.2022 16:10
          +1

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

          Пример из учебника:
          type Without<T, U> = T extends U ? never : T
          type A = Without<boolean | number | string, boolean>
          На выходе будет number | string, но если бы в книге это не расписали, я бы ни за что не догадался, что эта строчка делает такое вот. Здесь нужно понимать иерархию типов, кто чей предок, а кто потомок, чтобы понимать, кто кого extends. И почему-то частенько в описаниях пишут не T, а U extends T, но не объясняют, почему нам важны именно потомки типа. Типа, задел на будущее, чтобы функция работала с кем-то ещё?

          А ещё infer, который что-то достаёт из массивов. Вернее, из типов массивов:
          T extends (infer U)[] ? U : T, причём, они этот U даже в квадратных скобках не писали, он просто появляется тут.
          Для меня это белиберда, я не могу осознать, что тут происходит, а это всего лишь возврат типа элементов массива.

          На минимальном уровне я понял, как это использовать, могу описать функцию, которая принимает объект с парой ключей и возвращает объект с другой парой ключей или массив. Но если у меня будет стоять задача типизировать вообще всё, то я пока не знаю, с чего начать. Очень надеюсь на учебники, который ещё нашёл, а пока что читаю "Программируй & типизируй", там подход вообще другой, там вместо объявления type на каждый чих, всё время создают классы, пользуясь тем, что классы одновременно создают одноимённые типы, в результате рождаются выражения a: A = new A(), но это я примерно понимаю так, что то ли конструктор класса может вернуть что-то отличное от A (его родителя?), то ли автор не доверяет автовыведению типа TS и хочет явно писать, что A() вернёт объект типа A.
          Но с классами понимать становится проще, потому что они явно друг друга наследуют или реализуют интерфейс, а не абстрактное number extends number | undefined.


          1. marshinov
            22.01.2022 08:57

            Вот только классы != типы. После F# с типами в TS очень просто работать. Надо только делать поправку, что система типов гибче и навороченнее, но превращается в тыкву в рантайме. Для всякого метапрограммирования бывает неудобно:(


          1. LEXA_JA
            23.01.2022 14:32

            На самом деле в прикладном коде писать что сложнее примитивных дженериков особо и не надо. Обычно такие вещи нужны для библиотек или переиспользуемых модулей.

            Вообще на всё это можно смотреть как на программирование где типы - заначения а дженерики - функции.

            > И почему-то частенько в описаниях пишут не T, а U extends T, но не объясняют, почему нам важны именно потомки типа. Типа, задел на будущее, чтобы функция работала с кем-то ещё?

            Если я правильно понял то речь про код такого вида:
            type Foo<T extends string> = { /**/}
            или
            type Bar = { /**/ }
            type Baz<T extends Bar> = { /**/ }
            В данном случае extends выполняет роль типов в обычных функциях, ограничивае типы, которые мы можем подставить.


            1. Metotron0
              23.01.2022 16:57

              На счёт последнего, c extends, какая практическая польза от T extends string вместо string? Что может скрываться пот extends string? Какой-нибудь string | number подходит под это? Если да, тогда мы не сможем сделать .length у того, что имеет тип T, нам придётся проверять, кто именно там лежит.

              Или я зря вообще пытаюсь представить перемененную типа T, с которой будет что-то делаться?


              1. Alexandroppolus
                23.01.2022 17:15

                Тут забавная игра слов: на самом деле "extends" подразумевает возможное сужение типа, а не расширение. То есть в данном примере Т может быть либо строкой, либо типом "набор строковых значений" - type T = 'foo' | 'bar'. В любом случае, .length и прочее строковое будет присутствовать.


              1. LEXA_JA
                23.01.2022 18:36

                Нет, string | number не подойдет. Тут может быть как и просто string, так и строковый литерал, например Foo<"bar">

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

                type ToHandler<Name extends string> = Name extends `${infer FirstLetter}${infer Rest}`
                  ? `on${Uppercase<FirstLetter>}${Rest}Change`
                  : never
                type t = ToHandler<"name"> // "onNameChange"



                1. Metotron0
                  23.01.2022 21:57

                  Опять колдовство началось. Как типы могут оперировать данными и пересобирать строки? Нужна же какая-то реализация того, что написано. И как можно затипизировать регистры символов внутри строки? Это же всё string. Как TS проверит, что функция (a: string) => string реализовала именно такое преобразование, как тут написано в типах? Он же не умеет проверять логику работы. Соответственно, зачем мы вызываем ToHandler<"name">? Чтобы получить конкретный строковый литерал? Почему сразу его не записать?

                  Кто-то так пишет? Есть шанс встретить такое в готовом коде и без комментариев?


    1. Alexandroppolus
      21.01.2022 18:51
      +1

      Пример не очень полезный, прямо скажем. response.json() возвращает any, и здесь без проверки оно приводится к типу. С тем же успехом можно было написать не fetchApi<User[]>('/users'), а response.json() as User[].

      По хорошему, входящие данные надо бы проверять этим или этим, и только потом приводить к типу. Без проверки это дыра в типизации.


      1. dust
        21.01.2022 19:20

        Да, конечно, мы не говорим о полноценном приложении, хотя и в данном случае все окей, мы ожидаем в результате Promise<ResultType>, где ResultType это User[], то есть дыры в типизации конечно нет. Там еще много условностей вроде ошибок и некорректных ответов api. Но это упрощение для примера


      1. Ilusha
        22.01.2022 03:17

        Валидация контракта в real-time - это же совершенно другая задача.


  1. PaulIsh
    21.01.2022 17:10
    +1

    Если у вас уже есть большой проект на JS, то разом перевести его на TS будет проблематично. Лично я решаю задачу с нескольких сторон:

    1. Если что-то можно выделить из проекта в пакет, то создайте новый пакет небольшой функциональностью (например с публикацией в корпоративный gitlab) на TS.

    2. Добавьте анотации к функциям и включите ts-check в vscode.

    Пример анотации
        /**
         * Положить в кеш ответ на асинхронный запрос
         * @param {string} requestId - идентификатор запроса
         * @param {hl7.HL7Response} msg - сообщение ответ
         */
        async putAsyncCallRequest(requestId, msg) {
            ...
        }

    Такие анотации позволяют проверять типы и в javascript проекте.


  1. Ostic
    21.01.2022 18:36

    так учить TS точно НЕ надо
    так учить TS точно НЕ надо


    1. PavloPoliakov Автор
      21.01.2022 18:51

      Что не так с выделенной частью?


      1. Ostic
        21.01.2022 19:00
        +2

        давно ли ES стал "частью" TS? может я что-то пропустил?


      1. Ostic
        21.01.2022 19:08

        Если мы убираем TS, частью которого является JS, а JS остается. Противоречие.

        ещё и кто-то минусует ))


        1. navferty
          21.01.2022 20:15
          +3

          В первоначальной фразе имеется в виду что JS можно воспринимать как подмножество TS - то есть JS-код можно интерперетировать как валидный TS


          1. Ostic
            21.01.2022 20:25
            -2

            На мой вкус TS нужно воспринимать как своего рода "плагин-линтер", как некие "строительные леса" на период разработки, которые потом убирают. Собственно разработчики так его и позиционируют. Мало того с TS можно ещё и накосяить, чего без TS бы не было. Так что не всё так однозначно.


  1. RiverFlow
    22.01.2022 14:42
    -4

    С тайпскриптом все просто:

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

    Предполагалось что джава это конечное абсолютное решение для всего от кофемолки до шаттла и особенно энтерпрайза!

    За невменяемые бюджеты и сроки "гора" джава-чемпионов рожала мёртвых мышей важно приговаривая что вот уж сейчас вот на этот раз уж точно!

    А если работодатель нервничал, джава гуру гордо уходил в другую контору.

    Но пришёл 2012 и вообще всем и окончательно стало понятно что джава это как минимум чёрная дыра для времени и денег и рынок переключился на простой и понятный а главное дающий результаты - JS.

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

    Но джава ты успели поднатореть в риторике и привыкнуть к дурным деньгам и попыжившись с javafx, дюкскрипт и т. П. Ужасами тихо похоронили свою джаву плавно переползая на не знавшее паразитов тело js.

    Тайпскрипт это кусок ООП-говна шлепнутый вчерашним джавистами, анатомически не могучим осилить прикладное функционал не программирование и уверовавшими в библию паттернов, которая писалась сишниками!

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

    ООП в джаве это академическая ближе, эпохи первой волны тщеславия кодеров, когда топы из SUN пришли к сопляку Гослингу на поклон с предложением: сделай нам рай, как твоец душе угодно и за любые деньги!

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

    Тайпскрипт не пригоден ни для чего, кроме как играть роль прослойки для ООПнутых на всю голову в мир JS и как и в случае с джавоц вы не найдёте ни одного примера чегото действительно написанного и работающего на ТС фактически а не номинально.

    Так что вывод один : правильное изучение ТС это забыть и никогда не связываться с ТС!


    1. cuddlemeister
      22.01.2022 22:09
      +1

      вы не найдёте ни одного примера чегото действительно написанного и работающего на ТС фактически а не номинально.

      Не могу понять, все это жирный троллинг или реальное мнение?


    1. 1nt3g3r
      22.01.2022 22:31

      Хех, фронтендер детектед)

      Появится у вас проект на сотню-другую тысяч строк кода. Потом ещё пять. Это надо связать, появится какая-то Кафка. И вот внезапно вы уже забываете, что там вы вначале писали.

      Адекватная ООП архитектура поможет вам разобраться как оно организовано. Статический контроль типов а) не даст поломать существующий код б) IDE поможет вам сориентироваться что и куда приходит в ваши методы.

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


  1. SergeyPeskov
    23.01.2022 00:55

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

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

    Если вы работаете в команде, то не стоит так делать. При переводе проекта на typescript лучше сразу указывать корректные типы(даже если для этого требуется много времени). Сталкивался с тем, что временно указав в коде any, разработчик, потом его просто забывает исправить.


    1. Ilusha
      23.01.2022 02:50

      Вы слишком категоричны. Техдолг - это нормальный практичный инструмент. Да, его нужно отслуживать: всему есть цена. Но до этого нужно строить процессы.

      Можно найти множество причин разрешить писать any, и множество причин строго запретить и any, и ts-ignore: проекты разные, задачи разные, процессы, квалификация разрабов разная, цели разные и, самое главное: бюджеты и доступные ресурсы разные.


      1. SergeyPeskov
        23.01.2022 17:42

        Я согласен, что многое зависит от возможностей команды, просто у нас при переходе на typescript получилось слишком много кода с any. В результате очень сложно поддерживать код, который был написан другим разработчиком, даже исправление казалось бы простых багов в таком коде бывает занимает значительно больше времени, чем ожидалось.


        1. Ilusha
          23.01.2022 19:04

          Для этих мест с any, по факту, ничего не изменилось: был js, остался js. Сложность багов не выросла, но изменилось отношение к ним.

          Я уверен, что вы прекрасно понимаете, что можно поставить задачу на исправление any и улучшать кодовую базу. Просто - это долго. По пути всплывут минорные и потенциальные баги, нужен будет рефакторинг во многих местах. Где-то явные преобразования типов.