Comments 24
Спасибо за перевод. От себя добавлю несколько замечаний к самой статье:
TSLint давно заброшен и вместо него рекомендуется использовать ESLint;
Для организации кода лучше придерживаться использования модулей (ES Modules) вместо namespaces (см. ESLint
no-namespace
, официальную документацию, или Google styleguide)по поводу типа Object - важно не путать
Object
,object
и{}
. Например (см. в песочнице):const data1: Object = 123; // валидно const data2: {} = 456; // валидно const data3: object = 789; // невалидно !
Подробнее про это можно прочитать тут на Хабре или в том же Google styleguide
А так хорошие практики и советы по TypeScript описаны в недавно обновленном руководстве от Google TypeScript Style Guide (+ см. перевод руководства на русский язык) о котором упоминалось в статье на Хабре. Там куда больше рекомендаций, учитывающих опыт разработчиков из Google, согласованных между собой и собранных в единый документ.
За перевод, конечно, спасибо, но очень уж "сухая" статья получалась. Некоторые пункты могут быть сложно воспринимаемы для "осваивающего TypeScript". Будто у автора была задача охватить как можно больше материала, имея ограничение на количество символов в статье.
ограничении применения any до конкретных случаев, в которых тип действительно неизвестен
Для этого есть unknown
. Тип any
это легаси, потому что он не top и не bottom тип в решётке типов, а вообще непойми что.
будет нелишним добавлять утверждения типов или тайп гарды
Вместе с unknown
это так же чудесно работает.
Тип Object является встроенной возможностью TypeScript, позволяющей ссылаться на базовый тип объекта
Именно, он ссылается на базовый тип объекта, а для всех описанных ниже примеров нужен тип {}
: просто тип объекта. Подробнее тут.
let obj: Object = { name: "John", age: 30 };
И зачем мы тут потеряли информацию про типы полей?
let str: string = obj.name; // валидно
Ну попробуйте тут тип явно не указывать, ага.
function divide(numerator: number, denominator: number): number | never
number | never = number
, потому что это ноль для типа-суммы, и это совершенно абсурдное использование never
. Он будет иметь смысл, когда нужно написать тип значения, но нет ни одного пути исполнения кода, приводящего к наличию этого значения. Например, если функция всегда throw
, или в default
у switch
, где остальные case
разобрали все случаи.
Enums – это перечисления, позволяющие определять в TS набор именованных констант. С их помощью можно писать более читаемый и обслуживаемый код
Начиная с TS 5.0 enum ничем не отличается от union, во многом из-за их крайне нетривиального поведения в текущих версиях языка.
Пространства имён позволяют организовывать код и избегать коллизий в именовании его элементов
Пространства имён нужны для того, чтобы типизировать легаси конструкции с помощью declaration merging. Для организации кода они подходят существенно хуже модулей, и в реальном коде на TS практически не используются.
Лучшая практика 16: type guards
Отвратительная это практика, как as
и any
. Type guard говорит компилятору "когда я тут true верну, тут будет такой тип, зуб даю". Вся типобезопасность остаётся на совести программиста. Для типизации какого-то грязного старого кода на JS их использовать можно, а в повседневной практике категорически нельзя, нужно переписывать код. В качестве правила левого буравчика: возьмите disjoint union (он же tagged union, он же ADT).
К примеру, с помощью infer можно создать более точный тип для функции, возвращающей массив конкретного типа:
declare const f: <T,>(t: T[]) => T;
Смотри, мам, никаких infer! Conditional types нужны для типизации грязного старого кода на JS. Естественно, это не "лучшая практика".
type ReturnType = T extends (…args: any[]) => infer R ? R : any;
Во-первых, такой тип давно в язык добавили, и вы таким примером получите ошибку. Во-вторых, при переводе неплохо бы не лажать с лигатурами в коде вроде вот этого эллипсиса, из-за которого он вдвойне не откомпилируется. В-третьих, см. предыдущий пункт.
Лучшая практика 21: декораторы
Которые не соответствуют стандарту до выхода ещё не вышедшей версии 5.0. Отличная рекомендация, давайте немедленно ими воспользуемся, чтобы через месяц выбросить код и начать писать заново.
Заключение
Пожалуйста, не переводите чушь. Это просто перечисление глав из документации.
или в default у switch, где остальные case разобрали все случаи
Хм, а в чём принципиальное отличие if (denom === 0) throw от switch(denom !== 0) { case true, default }?
Ну то есть идею я вроде понял, но не уверен, что понимаю, где проходит граница между стоит / не стоит использовать never.
type guards
А что делать с объектами, пришедшими снаружи (с бэка или из JSON или от юзера, например)? Т.е. type guards тут существуют, насколько я понимаю, чтобы вместо objectFromBackend as any as BEData
мы писали
function isObjectFromBEValid(x): x is BEData {
return checkJSON(objectFromBackend, scheme);
}
А, в случае switch там симметрично получается. Если мы делаем switch (x.type)
где x: {type: 'a'} | {type: 'b'}
, то после case 'a': case 'b': return;
у нас останется x: never
.
что делать с объектами, пришедшими снаружи
Если API "правильно" разработан, для них хватит и обычного narrowing. Иногда бывает и так, что API писали без типизируемости и эффективности валидации в уме, и придётся действительно ваять type guards. Но использовать небезопасные фичи ad hoc и по месту -- плохая идея, и стоит попытаться абстрагировать это в какой-то хорошо протестированный библиотечный код. Для парсинга внешних данных такие библиотеки уже, конечно, написали. zod
вроде бы самая популярная из.
Best practice разработки на TS это всё-таки использовать как можно меньше фич языка, и использовать из них как можно более простые. Фичи вроде type guards и namespaces были добавлены в язык в основном для костыляния во время переезда проекта с JS. Безопасно предполагать, что если они вам где-то нужны, то либо на самом деле они не нужны, либо вы натолкнулись на баг, нашли/завели тикет, записали URL тикета в комментарий в коде, и попытались изолировать костыль вокруг этого бага с использованием этих фичей от остального кода.
Спасибо за перевод. От себя добавлю несколько замечаний к самой статье:
TSLint давно заброшен и вместо него рекомендуется использовать ESLint;
Для организации кода лучше придерживаться использования модулей (ES Modules) вместо namespaces (см. ESLint
no-namespace
, официальную документацию, или Google styleguide)по поводу типа Object - важно не путать
Object
,object
и{}
. Например (см. в песочнице):const data1: Object = 123; // валидно const data2: {} = 456; // валидно const data3: object = 789; // невалидно !
Подробнее про это можно прочитать тут на Хабре или в том же Google styleguide
А так хорошие практики и советы по TypeScript описаны в недавно обновленном руководстве от Google TypeScript Style Guide (+ см. перевод руководства на русский язык) о котором упоминалось в статье на Хабре. Там куда больше рекомендаций, учитывающих опыт разработчиков из Google, согласованных между собой и собранных в единый документ.
Перевод Google TypeScript Style Guide на русский машинный, очень некорректный и путающий местами
Лучше оригинал
Данный перевод, как ни странно делался вручную, шаг за шагом. О пояснении причин и особенностей перевода опубликована целая статья.
Т.к. это именно руководство, т.е. документ, то при переводе важно было соблюдать точность, поэтому перевод делался буквальным, а не адаптированным и поэтому текст может читаться тяжело, о чем даже явно было описано в статье. При адаптированном переводе есть огромная вероятность внести отсебятину, которая может внести смысловую ошибку, что для руководства недопустимо. Оригинал также читается местами тяжело, с чем столкнулся также и переводчик Google JavaScript Style Guide.
Помимо этого, была необходимость подогнать текст перевода в соответствии с терминами «MUST», «SHOULD», «MAY» с учетом RFC. Как раз в недавнем обновлении оригинала уделили много внимания выделению этих терминов, что сразу же было учтено и в обновлении перевода.
В статье я специально выделил момент с автоматическими переводчиками, поскольку они часто и «MUST» и «SHOULD» переводят как «ДОЛЖЕН», а данное слово в русском языке несет обязательный оттенок. Поэтому SHOULD был в документе перефразирован под RECOMMENDED, что не нарушает RFC2119, но позволяет явно разделить по смыслу ключевые слова на три группы "должен", "рекомендуется", "возможно" (хотя сейчас я думаю, что более подошло бы слово "допустимо").
Также в оригинале есть ошибки, которые учтены в переводе и описаны в примечании. Помимо этого, в перевод добавлено множество уточнений и скорректировано форматирование.
То что пунктуация может хромать - это вполне возможно, поскольку когда приходится шаг за шагом переводить подобный текст - в глазах это уже мылится. Если есть предложение, как что-то можно сделать более читаемым, но с обязательным сохранением всех деталей оригинальной фразы - рад буду учесть. В любом случае проект открыт и вы можете предложить пожелания или правки.
По возможности, уточните пожалуйста, где именно данный перевод некорректен?
Я сам переводил, знаю, какого это, и ни в коей мере не хотел наехать. Спасибо за труд.
Много не читал, но, например, что резануло сразу:
Импорты пространств имен модулей имеют верблюжий регистр (
lowerCamelCase
) в то время как файлы имеют змеиный регистр (snake_case
)
В первый раз вижу выражение "верблюжий регистр". Может в рускоязычной технической литературе это распространено, просто я не встречал.
Параметр типа
Я загуглил, такой перевод термина type parameter встречается в четыре раза реже, чем "верблюжий регистр". Мне кажется, искажается смысл. Более корректно - обобщенный тип, типово[ы]й параметр, типовая переменная.
Вы обратили внимание на очень хорошие моменты:
Верблюжий/змеиный регистр
Что касается "верблюжий/змеиный регистр" — такая формулировка нередко встречается в литературе. Термин "нотация" лучше (верблюжья/змеиная нотация), но такой термин нечасто используется по отношению к именованию файлов. Еще вариантом является просто слово "стиль", оно универсально и такой вариант использовался переводчиком Google JavaScript Style Guide.
В таком случае, оригинальную фразу вполне можно перевести так:
Импорты пространств имен модулей пишутся в стиле
lowerCamelCase
в то время как файлы именуются в стилеsnake_case
, что означает, что корректные импорты не будут совпадать по стилю написания с именами файлов.
Параметры типа
Вы подметили один из проблемных в плане перевода терминов.
Проблема в том, что слова "типовое/типовые" многими воспринимается как синоним "типичное" или "стандартное" (пример повседневной фразы: "типовое решение"), особенно среди новичков и тех, кто незнаком с дженериками. Такое слово всегда должно писаться с явным ударением: "ти́повые параметры".
Вариант в виде "Обобщенный тип" часто представляется просто как "обобщение или дженерик", а термин "generic" в руководстве уже используется.
В итоге вариант "параметры типа" остался как компромисный, тем более про них в руководстве совсем немного описано. Наверное для таких спорных по наименованию терминологий, в тексте перевода было бы правильней указывать сноски-ссылки на термин в вики, MDN и пр., чтобы развеять возможные неточности.
А так, благодарю вас за уделенное внимание и полезные замечания. Если у вас в дальнейшем появятся предложения или замечания по тексту, как вариант, вы можете создать issue, поскольку тут ветка комментариев может уйти далеко от темы автора.
Мне нравится JS. Пока что лучший язык лично для меня в плане синтаксиса: гибкий и простой. Разве что с Rust может потягаться, но тот слишком низкоуровневый.
Однако я не могу понять, в чем сакральный смысл ключ слов let, var?
Спасибо языку ";" можно не писать, как и var. Но не в TS, вроде как.
Однако я не могу понять, в чем сакральный смысл ключ слов let, var?
Тут достаточно хорошо объяснено https://learn.javascript.ru/var
Вообще то нет, там различия ключей. Возможно, я не совсем понятно выразился.
Зачем писать let, если можно не писать? Имхо дефолтное поведение новой переменной - своя область видимости. Что изменится, если не писать лишнее слово перед использованием переменной?
Разве что обратная совместимость ломается, хз.
Как вы с таким подходом будете ссылаться на переменную из наружной области видимости, а не заводить новую с тем же именем?
Как присвоить в переменную на две области видимости выше текущей, но не проинициализированную там?
Вы все верно отметили - дело в области видимости.
Создавая переменную без декларации, вроде "х = 42", вы загрязняете глобальную область видимости, т.к. переменная без декларации создается в глобальном объекте window, а не в том контексте, в котором вы ее создаете.
Так же, когда вы например задаете цикл с асинхронными действиями без let:
for (i = 0; i < 3; i++) {
setTimeout(() => {console.log(i)}, 0)
}
у вас будет вывод 3, 3, 3. Область видимости i
будет глобальной и сперва прокрутится цикл и наполнит очередь консоль логами, тк это синхронное действие. Потом освободится колстек и туда полетят выполняться консоль логи из очереди, используя эту единственную глобальную i
, которая будет равна 3 к этому моменту.
Но если использовать let
при декларации переменной i
, для каждой итерации цикла создастся своя область видимости со своим значением i
, которая выполнится корректно и вывод будет 0, 1, 2.
Кроме того, там вроде какие-то заморочки со скоростью выполнения JS с этим связаны, но я не особо в этом разбираюсь. Короче говоря, декларировать переменные корректно это хорошая практика.
Создавая переменную без декларации, вроде "х = 42", вы загрязняете глобальную область видимости, т.к. переменная без декларации создается в глобальном объекте window, а не в том контексте, в котором вы ее создаете.
Спасибо) Удивительно, но я об этом раньше либо не слышал либо не придавал значения...
С точки зрения логики было бы хорошо наоборот: создание переменной в глобальной области через ключевое слово, но иначе нарушится обратная совместимость. Чтош.
В спецификациях TS так же или там строгое ограничение на обязательное использование var let?
Нашел вот такую штуку для глобальных
https://devblogs.microsoft.com/typescript/announcing-typescript-3-4/#type-checking-for-globalthis
Сорри, TS пока только палкой потыкал метровой, хотя кажется что надо бы.
Не стоит просит прощения если не виновен) плохая привычка.
Если верить википедии: "TypeScript является обратно совместимым с JavaScript и компилируется в последний."
А это значит, что все, что пишется в JS - так же должно работать и в TS. Так что увы, в рамках TS от let отказа не будет. В TS поддерживается только расширение языка, но не замена его функционала.
Чтож, будем посмотреть и ждать переосмысления в другом языке. Ибо вангую - ключевое слово при объявлении переменной - это атавизм который рано или поздно отвалится.
Наблюдаю за этим языком - он очень сильно становится похож на C# (не удивительно), но шарп поддерживает куда больше фич и более громоздкий.
Если я правильно помню, то на ноде хорошим тоном считается писать в строгом режиме, прописывая "use strict" в первой строке каждого модуля. Это добавляет скорости исполнения ценой некоторых вольностей, таких как объявление переменных без команды let const var. Так что атавизмом можно считать именно отсутствие команды.
Кроме того рефакторить код не особо удобно, сложно отличить назначение переменной от ее изменения, сложно искать где что было задекларировано.
Не сложно, если подсветка синтаксиса будет правильно работать, например - подчеркивание при создании. Да и то особо не мешает.
Use strict не пишут (по крайней мере я не слышал), ts при компиляции в js автоматом добавляет. В js тож хз, я не пишу. И это не те языки, которые используют при высоких требованиях к производительности. Как и питон - это максимально простые языки, где удобство кодера выше ценится. Это же динамически типизированные, там и так производительность страдает. Питон так и вовсе интерпретируемый.
Осваиваем TypeScript: 21 лучшая практика при написании кода