Кодстайл фронтенда Эвотора ориентируется в первую очередь на официальный кодстайл Angular, поэтому настоятельно рекомендуем внимательно ознакомиться с этим документом.
Также рекомендуется ознакомиться с кратким списком Do’s and Don’ts от Typescript.
Ниже описаны ключевые моменты и особенности, которые не покрыты (или упомянуты вскользь) в основном кодстайле Angular.
Под сущностью в данном руководстве подразумевается:
Регистр | Использование |
---|---|
kebab-case или dashed-case |
названия файлов / селектор компонента |
PascalCase или UpperCamelCase |
классы / интерфейсы / типы / enum / декораторы / параметры типов |
lowerCamelCase или просто camelCase |
локальные переменные / параметры функций / функции / методы / свойства / селектор директивы |
UPPER_SNAKE_CASE или SCREAMING_SNAKE_CASE |
глобальные константы |
lower_snake_case или просто snake_case |
не используется в TypeScript |
Аббревиатуры в названиях сущностей НУЖНО рассматривать как целые слова (акронимы), поэтому не следует выделять их в названиях сущностей заглавными буквами (кроме случаев, когда это требуется внешним API):
Поэтому корректно писать
getFeedHtml
, а не getFeedHTML
loadApiUrl
, а не loadAPIURL
is
или has
.Хорошая статья на эту тему.
$
$
для названий свойств класса и переменных типа Observable
._
_
. В том числе, для обозначения приватных свойств класса.Единственный ДОПУСТИМЫЙ кейс использования префикса — инкапсуляция значения через публичный геттер / сеттер:
class AuthTokenStore {
private _token: string;
get token(): string {
return this._token;
}
}
При необходимости создания псевдонимов внутри класса для сущностей вне этого класса СЛЕДУЕТ наследовать регистр и название этой сущности
const LIMIT = 10;
class NewsListModel {
readonly LIMIT = LIMIT;
}
I
: INewsList
Interface
: NewsListInterface
Замечание: при создании интерфейса СЛЕДУЕТ подумать, для чего этот интерфейс вообще создаётся, и эту особенность нужно отразить в названии. При этом необходимо помнить про принцип разделения интерфейсов (ISP) — он поможет избежать перегруженных интерфейсов, которые не несут ничего, кроме отчаяния.
Для работы с API СЛЕДУЕТ использовать суффиксы Response
/ Request
:
NewsItemResponse
— для описания объекта ответа сервераNewsItemRequest
— для описания объекта тела запроса на серверЕсли метод или функция ничего не возвращают, НЕОБХОДИМО указать тип void
:
class NewsCardComponent implements OnInit {
// bad
ngOnInit() {
}
// good
ngOnInit(): void {
}
}
kebab-case
. Другой регистр НЕ ДОПУСКАЕТСЯ.<feature>.<type>.<extension>
.<type>
НЕОБХОДИМО использовать для следующих типов сущностей:
.module
.component
.directive
.service
.pipe
.decorator
.interceptor
.validator
.guard
.model
.collection
<type>
НЕ ДОПУСКАЕТСЯ: если тип сущности не указан выше, то суффикс не применяется.kebab-case
.classes
— для абстрактных и других вспомогательных классов (кроме классов моделей)components
— компонентыdirectives
— директивыconstants
— глобальные константыguards
— гарды роутераinterceptors
— интерсепторыinterfaces
— интерфейсыenums
— перечисленияtypes
— типыpages
— ЖЕЛАТЕЛЬНО выделить отдельную папку для компонентов страницpipes
— пайпыservices
— сервисыmodels
— классы моделей и коллекций предметной областиmodules
— модулиvalidators
— валидаторы формutils
— утилиты (классы со статичными методами)filename: product-type.enum.ts
export enum AppProductType {
}
Проблемы:
App
, в названии файла это не отражено.enum
public
ДОПУСКАЕТСЯ использовать только для парамеров конструктора класса.public
для свойств и методов класса избыточен — в Typescript всё публично по умолчанию.readonly
readonly
входящие и исходящие свойства компонентов (с @Input
и @Output
декораторами).readonly
псевдонимы внешних сущностей:
const LIMIT = 10;
class NewsListModel {
readonly LIMIT = LIMIT;
}
Компоненты, реализующие шаблон-контейнер раздела приложения.
-page
в блоке названия файла <feature>
.pages
.Пример структуры:
- pages
- home-page
- home-page.component.ts
- home-page.component.html
- home-page.component.scss
a += b
a -= b
a *= b
a /= b
a **= b
a ??= b
a ||= b
if
:
const t = 1;
// bad
(t > 0) && foo(t);
// good
if (t > 0) {
foo(t);
}
!!
(двойное отрицание)ДОПУСКАЕТСЯ приведение к булеву типу при помощи двойного отрицания:
function hasValue(value: string): boolean {
return !!value;
}
||
(логическое ИЛИ)ДОПУСКАЕТСЯ использование логического ИЛИ в операциях присваивания:
const valueWithFallback = value || 'fallback';
??
(нулевое слияние)ДОПУСКАЕТСЯ использование оператора нулевого слияния:
const valueWithFallback = value ?? 'fallback';
// bad
const bad1 = 11.1 | 0;
const bad2 = "11.1" | 0;
const bad3 = ~~"11.1";
// good
const good1 = parseInt(11.1, 10);
+
// bad
const badNum = +str;
// good
const goodNum = Number(str);
// good (if you know what you are doing)
const goodNum2 = parseInt(str, 10);
? :
class FormComponent {
// bad
setDisabledState(isDisabled: boolean): void {
isDisabled ? this.formControl.disable() : this.formControl.enable();
}
// good
setDisabledState(isDisabled: boolean): void {
const action = isDisabled ? 'disable' : 'enable';
this.formControl[action]();
}
// also good
setDisabledState(isDisabled: boolean): void {
this.formControl[isDisabled ? 'disable' : 'enable']();
}
}
const result = value
? Math.PI > 4
: 'nope';