TypeScript является популярным открытым языком программирования, идеально подходящим для современной разработки. За счёт своей продвинутой системы типов он позволяет писать более надёжный, обслуживаемый и масштабируемый код. Однако, чтобы задействовать весь потенциал этого языка и создавать высококачественные проекты, важно понимать лучшие практики и следовать им.
В этой статье мы углубимся в мир TypeScript и изучим 21 лучшую практику, с помощью которых вы сможете повысить свой навык работы с этим языком. Эти практики охватывают широкий спектр тем и сопровождаются конкретными вариантами применения в реальных проектах. Независимо от того, являетесь ли вы начинающим либо опытным разработчиком на TS, эта статья даст вам ценное понимание и рекомендации, которые помогут писать более чистый и эффективный код.
▍ Лучшая практика 1: строгая проверка типов
Начнём с самых азов. Представьте, что имеете возможность перехватывать потенциальные ошибки ещё до их возникновения. Звучит слишком хорошо, чтобы быть правдой? Но именно это позволяет вам получить строгая проверка типов в TS. Эта лучшая практика связана с перехватом тонких багов, которые тихо просачиваются в код и вызывают проблемы в дальнейшем.
Строгая проверка типов позволяет обеспечить, чтобы все переменные имели те типы, которые вы для них задумали. К примеру, это означает, что в случае объявления переменной с типом
string
TS убедится, чтобы присвоенное ей значение действительно было строкой, а не числом. Это помогает отлавливать ошибки на ранних стадиях и добиваться должной работы кода.Включить режим строгой проверки типов можно простой установкой параметра
“strict”: true
в файле tsconfig.json (по умолчанию должен быть true
). В результате TS активирует набор проверок, перехватывающих определённые ошибки, которые в противном случае, остались бы незамеченными.Вот пример того, как проверка типов может избавить вас от распространённой ошибки:
let userName: string = "John";
userName = 123; // TypeScript выбросит исключение, поскольку "123" не является строкой.
▍ Лучшая практика 2: вывод типов
TypeScript построен вокруг конкретного обозначения типов, но это не значит, что вы обязаны все определять явно.
Вывод типов – это возможность компилятора TS автоматически определять тип переменной на основе присвоенного ей значения. Это означает, что вам не нужно явно указывать тип переменной при каждом её объявлении. Вместо этого компилятор будет анализировать присвоенное ей значение и выводить соответствующий тип.
Например, в следующем фрагменте кода TS автоматически выведет тип
name
как string
:let name = "John";
Эта возможность особенно полезна при работе со сложными типами либо при инициализации переменной со значением, возвращаемым функцией.
Но здесь нужно помнить, что вывод типов – это не волшебная палочка, и иногда лучше указывать типы явно, особенно при работе с их сложными вариантами либо в случаях, когда требуется использовать определённый тип.
▍ Лучшая практика 3: линтеры
Линтеры – это инструменты, которые помогают улучшить код за счёт его проверки на соответствие набору правил и руководств.
Для TS существует несколько линтеров вроде TSLint и ESLint, которые помогают добиться согласованности стиля кода и перехватить потенциальные ошибки. Эти линтеры можно настроить на обнаружение таких ошибок, как упущенные точки с запятой, неиспользуемые переменные и не только.
▍ Лучшая практика 4: интерфейсы
Если говорить о написании чистого и обслуживаемого кода, то здесь лучшим помощником выступают интерфейсы. Они подобны схемам для ваших объектов, задающим структуру и свойства данных, с которыми вы работаете.
Интерфейс в TS определяет контракт для формы объекта. Он указывает свойства и методы, которыми объект данного типа должен обладать, и может использоваться в качестве типа для переменной. Это означает, что в случае присваивания объекта переменной с типом
interface
TS будет проверять, чтобы этот объект имел все указанные в данном интерфейсе свойства и методы.Вот пример определения и использования интерфейса:
interface User {
name: string;
age: number;
}
let user: User = {name: "John", age: 25};
Интерфейсы также упрощают рефакторинг кода, обеспечивая одновременное обновление всех участков, где используется определённый тип.
▍ Лучшая практика 5: псевдонимы типов (type alias)
TypeScript позволяет создавать кастомные типы, используя так называемые псевдонимы типов. Основное различие между ними и интерфейсами в том, что при использовании псевдонима мы создаём новое имя для типа, а интерфейс создаёт новое имя для формы объекта.
Например, псевдоним типа можно использовать, чтобы создать собственный тип для точки в двухмерном пространстве:
type Point = { x: number, y: number };
let point: Point = { x: 0, y: 0 };
С помощью псевдонимов типов также можно создавать сложные типы вроде объединённого типа или типа пересечения.
type User = { name: string, age: number };
type Admin = { name: string, age: number, privileges: string[] };
type SuperUser = User & Admin;
▍ Лучшая практика 6: кортежи
Кортежи позволяют создавать массив фиксированного размера с элементами разных типов, расположенных в определённом порядке.
К примеру, с помощью кортежа можно представить ту же точку в двухмерном пространстве:
let point: [number, number] = [1, 2];
С их помощью также можно представлять коллекцию элементов нескольких типов:
let user: [string, number, boolean] = ["Bob", 25, true];
Одно из основных свойств кортежей в том, что они дают возможность выразить конкретную связь между элементами коллекции и типами.
Помимо этого, с помощью деструктурирующего присваивания можно извлекать элементы кортежа и присваивать их переменным:
let point: [number, number] = [1, 2];
let [x, y] = point;
console.log(x, y);
▍ Лучшая практика 7: тип any
Иногда у нас нет всей информации о типе переменной, но использовать её в коде всё же нужно. В подобных случаях можно задействовать тип
any
. Однако, как и в случае с любым мощным инструментом, при использовании any
необходимо быть очень осторожным.Один из лучших приёмов тут заключается в ограничении применения
any
до конкретных случаев, в которых тип действительно неизвестен. Это бывает при работе со сторонними библиотеками или динамически генерируемыми данными. Кроме того, будет нелишним добавлять утверждения типов или тайп гарды (type guard), гарантируя правильное использование переменной. Также по возможности старайтесь максимально сузить тип переменной.Например:
function logData(data: any) {
console.log(data);
}
const user = { name: "John", age: 30 };
const numbers = [1, 2, 3];
logData(user); // { name: "John", age: 30 }
logData(numbers); // [1, 2, 3]
Ещё один полезный приём заключается в избегании использования
any
для аргументов функций и возвращаемых ей типов, поскольку он может понизить общую безопасность типов кода. Вместо этого рекомендуется использовать более конкретный тип либо более обобщённый вроде unknown
или object
, который обеспечит хоть какой-то уровень безопасности типов.▍ Лучшая практика 8: тип unknown
Unknown
– это рестриктивный тип, который был введён в TypeScript 3.0. Он более ограничен в сравнении с any
и может избавить вас от ряда непредвиденных ошибок.В отличие от
any
при использовании типа unknown
компилятор не даст выполнить какую-либо операцию для значения без предварительной проверки его типа. Это позволит перехватить ошибки типов во время компиляции, а не в среде выполнения.К примеру, тип
unknown
можно использовать для создания более типобезопасной функции:function printValue(value: unknown) {
if (typeof value === "string") {
console.log(value);
} else {
console.log("Not a string");
}
}
С его помощью также можно создавать более типобезопасные переменные:
let value: unknown = "hello";
let str: string = value; // Ошибка: тип 'unknown' нельзя присвоить типу 'string'.
▍ Лучшая практика 9: тип Object
Тип
Object
является встроенной возможностью TypeScript, позволяющей ссылаться на базовый тип объекта. С его помощью можно повысить типобезопасность кода, обеспечив наличие у всех объектов определённых свойств или методов.К примеру, этот тип можно использовать для создания более типобезопасной функции, получающей в качестве аргумента объект:
function printObject(obj: Object) {
console.log(obj);
}
Тип
Object
также можно использовать для создания более типобезопасных переменных:let obj: Object = { name: "John", age: 30 };
let str: string = obj.name; // валидно
let num: number = obj.age; // валидно
С помощью него вы можете обеспечить, чтобы все объекты обладали определёнными свойствами или методами, повысив тем самым типобезопасность кода.
▍ Лучшая практика 10: тип never
В TypeScript
never
является особым типом, представляющим значение, которое никогда не появится. С его помощью указывают, что функция вместо стандартного возвращения будет выбрасывать ошибку. Это отличный способ, позволяющий показать другим разработчикам (и компилятору), что функцию нельзя использовать определённым образом.Рассмотрите, к примеру, следующую функцию, выбрасывающую ошибку при знаменателе равном 0:
function divide(numerator: number, denominator: number): number {
if (denominator === 0) {
throw new Error("Cannot divide by zero");
}
return numerator / denominator;
}
Здесь функция
divide
объявлена как возвращающая число, но если знаменатель равен нулю, она выбрасывает ошибку. Чтобы обозначить невозможность в таком случае выполнить возврат, можно использовать в качестве возвращаемого типа never
:function divide(numerator: number, denominator: number): number | never {
if (denominator === 0) {
throw new Error("Cannot divide by zero");
}
return numerator / denominator;
}
▍ Лучшая практика 11: оператор keyof
Оператор
keyof
является эффективной возможностью TypeScript, позволяющей создавать тип, представляющий ключи объекта. С его помощью можно прояснять, какие свойства являются для объекта допустимыми.К примеру,
keyof
можно использовать для создания более читаемого и обслуживаемого типа объекта:interface User {
name: string;
age: number;
}
type UserKeys = keyof User; // "name" | "age"
С его помощью также можно создавать более типобезопасные функции, получающие в качестве аргументов объект и ключ:
function getValue<T, K extends keyof T>(obj: T, key: K) {
return obj[key];
}
let user: User = { name: "John", age: 30 };
console.log(getValue(user, "name")); // "John"
console.log(getValue(user, "gender")); // Ошибка: аргумент с типом '"gender"' нельзя присвоить параметру с типом '"name" | "age"'.
▍ Лучшая практика 12: Enums
Enums
– это перечисления, позволяющие определять в TS набор именованных констант. С их помощью можно писать более читаемый и обслуживаемый код, давая набору связанных значений имя, отражающее общий смысл.К примеру, с помощью
enum
можно определить набор возможных значений статуса заказа:enum OrderStatus {
Pending,
Processing,
Shipped,
Delivered,
Cancelled
}
let orderStatus: OrderStatus = OrderStatus.Pending;
Перечисления также могут иметь кастомный набор численных значений или строк.
enum OrderStatus {
Pending = 1,
Processing = 2,
Shipped = 3,
Delivered = 4,
Cancelled = 5
}
let orderStatus: OrderStatus = OrderStatus.Pending;
Соглашение об именовании требует называть перечисления с заглавной буквы и указывать их всегда в единственном числе.
▍ Лучшая практика 13: пространства имён
Пространства имён позволяют организовывать код и избегать коллизий в именовании его элементов. С их помощью можно создать для кода контейнер, в котором определить переменные, классы, функции и интерфейсы.
Например, пространства имён можно использовать для группировки всего кода, связанного с определённым функционалом:
namespace OrderModule {
export class Order { /* … */ }
export function cancelOrder(order: Order) { /* … */ }
export function processOrder(order: Order) { /* … */ }
}
let order = new OrderModule.Order();
OrderModule.cancelOrder(order);
Пространства имён также позволяют предотвратить коллизии в именовании за счёт присваивания фрагменту кода уникального имени:
namespace MyCompany.MyModule {
export class MyClass { /* … */ }
}
let myClass = new MyCompany.MyModule.MyClass();
Важно отметить, что пространства имён аналогичны модулям, но используются для организации кода и избежания коллизий, в то время как модули служат для загрузки и выполнения кода.
▍ Лучшая практика 14: вспомогательные типы (utility types)
Вспомогательные типы – это встроенная возможность TS, которая предоставляет набор готовых типов, позволяя писать более типобезопасный код. С их помощью можно более удобно выполнять стандартные операции над типами.
К примеру, вспомогательный тип
Pick
можно использовать для извлечения подмножества свойств из типа объекта:type User = { name: string, age: number, email: string };
type UserInfo = Pick<User, "name" | "email">;
Exclude позволяет удалять из типа объекта свойства:
type User = { name: string, age: number, email: string };
type UserWithoutAge = Exclude<User, "age">;
Partial даёт возможность сделать все свойства типа необязательными:
type User = { name: string, age: number, email: string };
type PartialUser = Partial<User>;
▍ Лучшая практика 15: Readonly и ReadonlyArray
При работе с данными в TypeScript иногда требуется обеспечить неизменность определённых значений, и здесь на помощь приходят ключевые слова
Readonly
и ReadonlyArray
.С помощью
Readonly
мы переводим свойства объекта в состояние «только для чтения», исключая возможность их изменения после создания. Такой приём пригождается, например, при работе с конфигурацией или постоянными значениями.interface Point {
x: number;
y: number;
}
let point: Readonly<Point> = {x: 0, y: 0};
point.x = 1; // TypeScript выдаст ошибку, поскольку "point.x" является read-only
ReadonlyArray
аналогично Readonly
, но используется для массивов.let numbers: ReadonlyArray<number> = [1, 2, 3];
numbers.push(4); // TypeScript выдаст ошибку, поскольку "numbers" является read-only
▍ Лучшая практика 16: type guards
При работе со сложными типами бывает сложно отслеживать возможные вариации переменной. В этом случае у нас есть тайп-гарды, которые позволяют сузить тип переменной на основе определённых условий.
Вот пример использования тайп-гарда для проверки, является ли переменная числом:
function isNumber(x: any): x is number {
return typeof x === "number";
}
let value = 3;
if (isNumber(value)) {
value.toFixed(2); // Благодаря тайп-гарду TypeScript знает, что "value" является числом.
}
Тайп-гарды также можно использовать с операторами
in
, typeof
и instanceof
.▍ Лучшая практика 17: обобщённые типы (generics)
Дженерики позволяют писать более универсальный код, способный работать с любым типом. С их помощью можно написать одну функцию, класс либо интерфейс, работающие с несколькими типами.
К примеру, обобщённую функцию можно использовать для создания массива любого типа:
function createArray<T>(length: number, value: T): Array<T> {
let result = [];
for (let i = 0; i < length; i++) {
result[i] = value;
}
return result;
}
let names = createArray<string>(3, "Bob");
let numbers = createArray<number>(3, 0);
С помощью дженериков также можно создать класс, способный работать с любым типом данных:
class GenericNumber<T> {
zeroValue: T;
add: (x: T, y: T) => T;
}
let myGenericNumber = new GenericNumber<number>();
myGenericNumber.zeroValue = 0;
myGenericNumber.add = function(x, y) { return x + y; };
▍ Лучшая практика 18: ключевое слово infer
Ключевое слово
infer
представляет собой продвинутую возможность TS, позволяет извлечь тип переменной в отдельный тип.К примеру, с помощью
infer
можно создать более точный тип для функции, возвращающей массив конкретного типа:type ArrayType<T> = T extends (infer U)[] ? U : never;
type MyArray = ArrayType<string[]>; // MyArray имеет тип string
С помощью него также можно создавать более точный тип для функции, возвращающей объект с конкретным свойством:
type ObjectType<T> = T extends { [key: string]: infer U } ? U : never;
type MyObject = ObjectType<{ name: string, age: number }>; // MyObject имеет тип {name:string, age: number}
▍ Лучшая практика 19: условные типы
С помощью условных типов можно создавать новые типы на основе условий других типов, выражая тем самым сложные отношения между ними.
К примеру, условный тип можно использовать для получения возвращаемого типа функции:
type ReturnType<T> = T extends (…args: any[]) => infer R ? R : any;
type R1 = ReturnType<() => string>; // string
type R2 = ReturnType<() => void>; // void
Помимо этого, с их помощью можно получать свойства типа объекта, отвечающие конкретному условию:
type PickProperties<T, U> = { [K in keyof T]: T[K] extends U ? K : never }[keyof T];
type P1 = PickProperties<{ a: number, b: string, c: boolean }, string | number>; // "a" | "b"
▍ Лучшая практика 20: отображённые типы
Отображённые типы представляют способ создания новых типов на основе существующих путём применения ряда операций к их свойствам.
К примеру, с помощью отображённого типа можно получить новый тип, представляющий вариацию существующего, но уже только для чтения:
type Readonly<T> = { readonly [P in keyof T]: T[P] };
let obj: { a: number, b: string } = { a: 1, b: "hello" };
let readonlyObj: Readonly<typeof obj> = { a: 1, b: "hello" };
Они также позволяют создавать новый тип, представляющий опциональную версию существующего:
type Optional<T> = { [P in keyof T]?: T[P] };
let obj: { a: number, b: string } = { a: 1, b: "hello" };
let optionalObj: Optional<typeof obj> = { a: 1 };
Отображённые типы можно использовать по-разному: для создания новых, а также для добавления, удаления или изменения свойств имеющихся.
▍ Лучшая практика 21: декораторы
Декораторы представляют способ внесения дополнительной функциональности в класс, метод или свойство при помощи простого синтаксиса. Они позволяют расширить поведение класса без изменения его реализации.
К примеру, с помощью декоратора можно добавить в метод логирование:
function logMethod(target: any, propertyKey: string, descriptor: PropertyDescriptor) {
let originalMethod = descriptor.value;
descriptor.value = function(…args: any[]) {
console.log(`Calling ${propertyKey} with args: ${JSON.stringify(args)}`);
let result = originalMethod.apply(this, args);
console.log(`Called ${propertyKey}, result: ${result}`);
return result;
}
}
class Calculator {
@logMethod
add(x: number, y: number): number {
return x + y;
}
}
Декораторы также позволяют добавлять в класс, метод или свойство метаданные, которые могут использоваться в среде выполнения.
function setApiPath(path: string) {
return function (target: any) {
target.prototype.apiPath = path;
}
}
@setApiPath("/users")
class UserService {
// …
}
console.log(new UserService().apiPath); // "/users"
▍ Заключение
Надеюсь, что эта статья оказалась для вас полезна и расширила ваше понимание того, как можно писать более чистый и эффективный код.
Помните, всё это лишь лучшие практики и руководства, а не жёсткие правила, поэтому в первую очередь здесь нужно опираться на собственное разумение и здравый смысл. Также важно учитывать, что по мере развития этого языка и появления в нём новых возможностей лучшие практики могут меняться, а значит — нужно быть готовым к освоению нового.
Играй в нашу новую игру прямо в Telegram!
Комментарии (22)
ht-pro
27.01.2023 22:13+2За перевод, конечно, спасибо, но очень уж "сухая" статья получалась. Некоторые пункты могут быть сложно воспринимаемы для "осваивающего TypeScript". Будто у автора была задача охватить как можно больше материала, имея ограничение на количество символов в статье.
TataSysueva
28.01.2023 06:47+1Как осваивающий – поддерживаю! Хотелось бы видеть больше примеров) но статья всё равно хорошая.
maeris
28.01.2023 00:41+15ограничении применения 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. Отличная рекомендация, давайте немедленно ими воспользуемся, чтобы через месяц выбросить код и начать писать заново.
Заключение
Пожалуйста, не переводите чушь. Это просто перечисление глав из документации.
Keyten
28.01.2023 16:54или в 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); }
maeris
29.01.2023 01:19+2А, в случае 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 тикета в комментарий в коде, и попытались изолировать костыль вокруг этого бага с использованием этих фичей от остального кода.
olegbarabanov
28.01.2023 01:13+8Спасибо за перевод. От себя добавлю несколько замечаний к самой статье:
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, согласованных между собой и собранных в единый документ.
gmtd
28.01.2023 09:53+1Перевод Google TypeScript Style Guide на русский машинный, очень некорректный и путающий местами
Лучше оригиналolegbarabanov
28.01.2023 12:41+1Данный перевод, как ни странно делался вручную, шаг за шагом. О пояснении причин и особенностей перевода опубликована целая статья.
Т.к. это именно руководство, т.е. документ, то при переводе важно было соблюдать точность, поэтому перевод делался буквальным, а не адаптированным и поэтому текст может читаться тяжело, о чем даже явно было описано в статье. При адаптированном переводе есть огромная вероятность внести отсебятину, которая может внести смысловую ошибку, что для руководства недопустимо. Оригинал также читается местами тяжело, с чем столкнулся также и переводчик Google JavaScript Style Guide.
Помимо этого, была необходимость подогнать текст перевода в соответствии с терминами «MUST», «SHOULD», «MAY» с учетом RFC. Как раз в недавнем обновлении оригинала уделили много внимания выделению этих терминов, что сразу же было учтено и в обновлении перевода.
В статье я специально выделил момент с автоматическими переводчиками, поскольку они часто и «MUST» и «SHOULD» переводят как «ДОЛЖЕН», а данное слово в русском языке несет обязательный оттенок. Поэтому SHOULD был в документе перефразирован под RECOMMENDED, что не нарушает RFC2119, но позволяет явно разделить по смыслу ключевые слова на три группы "должен", "рекомендуется", "возможно" (хотя сейчас я думаю, что более подошло бы слово "допустимо").
Также в оригинале есть ошибки, которые учтены в переводе и описаны в примечании. Помимо этого, в перевод добавлено множество уточнений и скорректировано форматирование.
То что пунктуация может хромать - это вполне возможно, поскольку когда приходится шаг за шагом переводить подобный текст - в глазах это уже мылится. Если есть предложение, как что-то можно сделать более читаемым, но с обязательным сохранением всех деталей оригинальной фразы - рад буду учесть. В любом случае проект открыт и вы можете предложить пожелания или правки.
По возможности, уточните пожалуйста, где именно данный перевод некорректен?
gmtd
28.01.2023 20:41+2Я сам переводил, знаю, какого это, и ни в коей мере не хотел наехать. Спасибо за труд.
Много не читал, но, например, что резануло сразу:
Импорты пространств имен модулей имеют верблюжий регистр (
lowerCamelCase
) в то время как файлы имеют змеиный регистр (snake_case
)В первый раз вижу выражение "верблюжий регистр". Может в рускоязычной технической литературе это распространено, просто я не встречал.
Параметр типа
Я загуглил, такой перевод термина type parameter встречается в четыре раза реже, чем "верблюжий регистр". Мне кажется, искажается смысл. Более корректно - обобщенный тип, типово[ы]й параметр, типовая переменная.
olegbarabanov
29.01.2023 04:45+2Вы обратили внимание на очень хорошие моменты:
Верблюжий/змеиный регистр
Что касается "верблюжий/змеиный регистр" — такая формулировка нередко встречается в литературе. Термин "нотация" лучше (верблюжья/змеиная нотация), но такой термин нечасто используется по отношению к именованию файлов. Еще вариантом является просто слово "стиль", оно универсально и такой вариант использовался переводчиком Google JavaScript Style Guide.
В таком случае, оригинальную фразу вполне можно перевести так:
Импорты пространств имен модулей пишутся в стиле
lowerCamelCase
в то время как файлы именуются в стилеsnake_case
, что означает, что корректные импорты не будут совпадать по стилю написания с именами файлов.Параметры типа
Вы подметили один из проблемных в плане перевода терминов.
Проблема в том, что слова "типовое/типовые" многими воспринимается как синоним "типичное" или "стандартное" (пример повседневной фразы: "типовое решение"), особенно среди новичков и тех, кто незнаком с дженериками. Такое слово всегда должно писаться с явным ударением: "ти́повые параметры".
Вариант в виде "Обобщенный тип" часто представляется просто как "обобщение или дженерик", а термин "generic" в руководстве уже используется.
В итоге вариант "параметры типа" остался как компромисный, тем более про них в руководстве совсем немного описано. Наверное для таких спорных по наименованию терминологий, в тексте перевода было бы правильней указывать сноски-ссылки на термин в вики, MDN и пр., чтобы развеять возможные неточности.
А так, благодарю вас за уделенное внимание и полезные замечания. Если у вас в дальнейшем появятся предложения или замечания по тексту, как вариант, вы можете создать issue, поскольку тут ветка комментариев может уйти далеко от темы автора.
ildarin
28.01.2023 01:15Мне нравится JS. Пока что лучший язык лично для меня в плане синтаксиса: гибкий и простой. Разве что с Rust может потягаться, но тот слишком низкоуровневый.
Однако я не могу понять, в чем сакральный смысл ключ слов let, var?
Спасибо языку ";" можно не писать, как и var. Но не в TS, вроде как.
FlyHighOnTheSky
28.01.2023 01:29Однако я не могу понять, в чем сакральный смысл ключ слов let, var?
Тут достаточно хорошо объяснено https://learn.javascript.ru/var
ildarin
28.01.2023 02:31Вообще то нет, там различия ключей. Возможно, я не совсем понятно выразился.
Зачем писать let, если можно не писать? Имхо дефолтное поведение новой переменной - своя область видимости. Что изменится, если не писать лишнее слово перед использованием переменной?
Разве что обратная совместимость ломается, хз.
maeris
28.01.2023 08:51Как вы с таким подходом будете ссылаться на переменную из наружной области видимости, а не заводить новую с тем же именем?
Как присвоить в переменную на две области видимости выше текущей, но не проинициализированную там?
acsent1
28.01.2023 10:29+1Как я понимаю предлагается let не писать, чтобы компьютер сам его подставлял в уме. А если нужно var, то нужно писать
Homiakin
28.01.2023 11:51+3Вы все верно отметили - дело в области видимости.
Создавая переменную без декларации, вроде "х = 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 с этим связаны, но я не особо в этом разбираюсь. Короче говоря, декларировать переменные корректно это хорошая практика.ildarin
28.01.2023 20:50Создавая переменную без декларации, вроде "х = 42", вы загрязняете глобальную область видимости, т.к. переменная без декларации создается в глобальном объекте window, а не в том контексте, в котором вы ее создаете.
Спасибо) Удивительно, но я об этом раньше либо не слышал либо не придавал значения...
С точки зрения логики было бы хорошо наоборот: создание переменной в глобальной области через ключевое слово, но иначе нарушится обратная совместимость. Чтош.
ildarin
28.01.2023 20:54В спецификациях TS так же или там строгое ограничение на обязательное использование var let?
Нашел вот такую штуку для глобальных
https://devblogs.microsoft.com/typescript/announcing-typescript-3-4/#type-checking-for-globalthis
Homiakin
28.01.2023 22:22Сорри, TS пока только палкой потыкал метровой, хотя кажется что надо бы.
ildarin
28.01.2023 23:24Не стоит просит прощения если не виновен) плохая привычка.
Если верить википедии: "TypeScript является обратно совместимым с JavaScript и компилируется в последний."
А это значит, что все, что пишется в JS - так же должно работать и в TS. Так что увы, в рамках TS от let отказа не будет. В TS поддерживается только расширение языка, но не замена его функционала.
Чтож, будем посмотреть и ждать переосмысления в другом языке. Ибо вангую - ключевое слово при объявлении переменной - это атавизм который рано или поздно отвалится.
Наблюдаю за этим языком - он очень сильно становится похож на C# (не удивительно), но шарп поддерживает куда больше фич и более громоздкий.
Homiakin
29.01.2023 02:31Если я правильно помню, то на ноде хорошим тоном считается писать в строгом режиме, прописывая "use strict" в первой строке каждого модуля. Это добавляет скорости исполнения ценой некоторых вольностей, таких как объявление переменных без команды let const var. Так что атавизмом можно считать именно отсутствие команды.
Кроме того рефакторить код не особо удобно, сложно отличить назначение переменной от ее изменения, сложно искать где что было задекларировано.
ildarin
29.01.2023 05:06Не сложно, если подсветка синтаксиса будет правильно работать, например - подчеркивание при создании. Да и то особо не мешает.
Use strict не пишут (по крайней мере я не слышал), ts при компиляции в js автоматом добавляет. В js тож хз, я не пишу. И это не те языки, которые используют при высоких требованиях к производительности. Как и питон - это максимально простые языки, где удобство кодера выше ценится. Это же динамически типизированные, там и так производительность страдает. Питон так и вовсе интерпретируемый.
olegbarabanov
Спасибо за перевод. От себя добавлю несколько замечаний к самой статье:
TSLint давно заброшен и вместо него рекомендуется использовать ESLint;
Для организации кода лучше придерживаться использования модулей (ES Modules) вместо namespaces (см. ESLint
no-namespace
, официальную документацию, или Google styleguide)по поводу типа Object - важно не путать
Object
,object
и{}
. Например (см. в песочнице):Подробнее про это можно прочитать тут на Хабре или в том же Google styleguide
А так хорошие практики и советы по TypeScript описаны в недавно обновленном руководстве от Google TypeScript Style Guide (+ см. перевод руководства на русский язык) о котором упоминалось в статье на Хабре. Там куда больше рекомендаций, учитывающих опыт разработчиков из Google, согласованных между собой и собранных в единый документ.