Компоненты
При декомпозции проекта выделяются повторно используемые компоненты. Такие компоненты абстрагированы от бизнес логики приложения. В соответсвии с переданными свойствами выполняют рендер html разметки и предоставляют обратную связь на различные действия (клики, ввод с клавиатуры и др.).
Файловая структура
Повторно используемые компоненты создаются в директории ./components
.
В больших проектах создаются поддиректории для группировки компонентов по назначению, например:
элементы, формы, разметки, списки, меню.
components
├── elements
| ├── component-name
| ├── button
| ├── icon
| └── logo
├── forms
└── layouts
Каждый компонент в своей папке. Название папки в соответствии с названием класса компонента, но в нижнем регистре через дефис (kebbab стиль).
Компонент ComponentName
component-name
├── index.js // допустимо component-name.js
└── style.less
- В
index.js
илиcompoent-name.js
класс компонента. Расширение.js
. - В
style.less
стили компонента. По умолчанию используем.less
, допустимы другие.
Название файлов и директорий всегда в нижнем регистре, чтобы избежать проблем с регистром на разных
платформах. Именование index.js
рекомендуется для быстроты копипастов, единства внутренней структуры, коротких
импортов. Именование component-name.js
если в IDE/отладке неудобно различать файлы или index.js
нужен для
реэкспорта.
В директории могут быть другие ресурсы компонента, в том числе вложенные компоненты, если они используются только в рамках компонента (его составные части).
component-name
├── assets
| └── bg.png
├── sub-component
| ├── index.js
| └── style.less
├── index.js
└── style.less
Класс компонента
Компонент определяется через класс. Опредление через функцию нежелательно, чтобы экономить время при доработки внутренней логики и иметь единую структуру всех компонент.
Для оптимизации рендера использовать
shouldComponentUpdate()
,React.PureComponent
и не делать вычислений вrender()
Название класса всегда с большой буквы (CamelCase).
Начинается с обобщенного названия и последующими частями уточняется назначение/особенность компонента.
Например: Button
, ButtonNext
, LayoutPage
, MenuTop
, FormNewsCreate
.
Польза в быстром улавливании сути компонентов, в упорядоченности файлов.
Допустимы исключения, например RadioButton
общепринятое имя (можно считать обобщенным названием).
class MenuTop extend Component {
}
export MenuTop;
Описание входных свойств делать в начале класса статическим свойством propTypes
вместо его
присвоения после определения класса. Статических свойств нет в стандарте ES2016/2017, но через
babel поддерживаются.
class MenuTop extend Component {
static propTypes = {
items: PropTypes.array
}
}
Стили
Использовать БЭМ. Название класса блока соответствует классу компонента для консистентности и используется в первом теге рендера.
.MenuTop
Внутренняя разметка - это элементы, им названия css классов через двойное подчеркивание.
.MenuTop__item
.
Для задания модификаций блоку или элементу использовать одно нижнее подчёркивание.
.MenuTop_theme_cool
class MenuTop extend Component {
render(){
return (
<div className="MenuTop MenuTop_theme_cool">
<div className="MenuTop__item">
</div>
<div className="MenuTop__item MenuTop__item_active">
</div>
</div>
);
}
}
По БЭМ элементы есть только у блока. Элементов у элементов нет. Если по разметке что-то подобное напрашивается, то речь идет о вложенном блоке. В простых случаях его можно определить css классом в рамках компонента, но лучше подумать о выносе в отдельный компонент.
Для указания нескольких классов или с условием использовать библиотеку
classnames, импортируя её в короткую константу cs
import cs from "classnames";
class MenuTop extend Component {
render(){
return (
<div className="MenuTop">
<div className="MenuTop__item">
</div>
<div className={cn("MenuTop__item", {"MenuTop__item_active": this.props.isActive})}>
</div>
</div>
);
}
}
События
Функции обратного вызова (callbacks), используемые для событий DOM/компонент, определять методами класса стрелочным способом.
class MenuTop extend Component {
onClick = () => {
};
}
В ряде случаях используется каррирование для замыкания параметра, определяемого при рендере. Тогда стрелочное определение метода не требуется.
class MenuTop extend Component {
onClick(key){
return (e) => {
e.preventDefault();
this.prop.doSome(key);
}
}
renderItem(item){
return (
<li key={item.key} onClick={this.onClick(item.key)}></li>
)
}
}
Нужно учитывать, что при каждом рендере будет создаваться новая функция-обработчик
Если внешний обработчик (callback из props) вешается на DOM событие, то нужно переопределять его, не отдавая во вне объект DOM события. Например, компонент поля ввода в событии onChange должен вернуть значение поля.
class Input extend Component {
onChange(e) => {
if (this.props.onChange){
this.props.onChange(e.target.value);
}
}
render(){
return (
<input onChange={this.onChange} value={this.props.value}>
)
}
}
Чтобы не проверять, определено ли необязательное свойство в this.props
, рекомендуется описывать
значения по умолчанию после определения propTypes
class Input extend Component {
static propTypes = {
onChange: PropTypes.func
}
static defaultProps = {
onChange: ()=>{}
}
}