# Руководство по написанию React/JSX кода от [Airbnb](https://github.com/airbnb/javascript/react) *Наиболее разумный подход к написанию React и JSX* Это руководство основано на стандартах, которые в настоящее время преобладают в JavaScript сообществе, хотя некоторые соглашения (например, async/await или статические поля класса) могут все ещё быть включены или запрещены в каждом отдельном случае. В настоящее время что-либо, что не дошло до 3-ей стадии (stage 3) стандарта, не включено и не рекомендуется в этом руководстве. ## Оглавление 1. [Основные правила](#basic-rules) 1. [Class против `React.createClass` против компонента без состояния (stateless)](#class-vs-reactcreateclass-vs-stateless) 1. [Примеси (mixins)](#mixins) 1. [Именование](#naming) 1. [Объявление](#declaration) 1. [Выравнивание](#alignment) 1. [Кавычки](#quotes) 1. [Пробелы](#spacing) 1. [Свойства (Props)](#props) 1. [Ссылки (Refs)](#refs) 1. [Круглые скобки](#parentheses) 1. [Теги](#tags) 1. [Методы](#methods) 1. [Последовательность](#ordering) 1. [`isMounted`](#ismounted) ## <a name="basic-rules">Основные правила</a> - Включайте только один React компонент в файл. - Однако, разрешается несколько [компонентов без состояний (чистых)](https://facebook.github.io/react/docs/reusable-components.html#stateless-functions) в файле. eslint: [`react/no-multi-comp`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-multi-comp.md#ignorestateless). - Всегда используйте JSX синтаксис. - Не используйте `React.createElement`, если вы только не инициализируете программу из файла, который не является JSX. - [`react/forbid-prop-types`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/forbid-prop-types.md) разрешит `arrays` и `objects` только, если явно указано, что содержат `array` и `object`, используя `arrayOf`, `objectOf` или `shape`. ## <a name="#class-vs-reactcreateclass-vs-stateless">Class против `React.createClass` против компонента без состояния (stateless)</a> - Если у вас есть внутреннее состояние (`state`) и/или ссылки (`refs`), отдавайте предпочтение `class extends React.Component` вместо `React.createClass`. eslint: [`react/prefer-es6-class`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-es6-class.md) [`react/prefer-stateless-function`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/prefer-stateless-function.md) ```jsx // плохо const Listing = React.createClass({ // ... render() { return <div>{this.state.hello}</div>; } }); // хорошо class Listing extends React.Component { // ... render() { return <div>{this.state.hello}</div>; } } ``` И если у вас нет состояния (`state`) или ссылок (`refs`), отдавайте предпочтение нормальным функциям (не стрелочным) над классами: ```jsx // плохо class Listing extends React.Component { render() { return <div>{this.props.hello}</div>; } } // плохо (не рекомендуется делать выводы, опираясь на название функции) const Listing = ({ hello }) => ( <div>{hello}</div> ); // хорошо function Listing({ hello }) { return <div>{hello}</div>; } ``` ## <a name="mixins">Примеси (mixins)</a> - [Не используйте примеси](https://facebook.github.io/react/blog/2016/07/13/mixins-considered-harmful.html). > Почему? Примеси вносят неявные зависимости, становятся причиной конфликтов имён и быстрого роста сложности. Для большинства случаев, в которых используются примеси, можно более эффективно применить компоненты, компоненты высшего порядка или вспомогательные модули. ## <a name="naming">Именование</a> - **Расширения**: Используйте расширение `.jsx` для компонентов React. eslint: [`react/jsx-filename-extension`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-filename-extension.md) - **Имя файла**: Используйте `PascalCase` для названий файлов, например, `ReservationCard.jsx`. - **Именование переменной**: Используйте `PascalCase` для компонентов React и `camelCase` для их экземпляров. eslint: [`react/jsx-pascal-case`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-pascal-case.md) ```jsx // плохо import reservationCard from './ReservationCard'; // хорошо import ReservationCard from './ReservationCard'; // плохо const ReservationItem = <ReservationCard />; // хорошо const reservationItem = <ReservationCard />; ``` - **Именование компонента**: Называйте файлы так же как и компоненты. Например, `ReservationCard.jsx` должен содержать внутри компонент `ReservationCard`. Однако корневые компоненты в директории должны лежать в файле `index.jsx`, и в этом случае название папки должно быть таким же, как название компонента: ```jsx // плохо import Footer from './Footer/Footer'; // плохо import Footer from './Footer/index'; // хорошо import Footer from './Footer'; ``` - **Именование компонента высшего порядка**: Используйте сочетание имени компонента высшего порядка и имени переданного в него компонента как свойство `displayName` сгенерированного компонента. Например, из компонента высшего порядка `withFoo()`, которому передан компонент `Bar`, должен получаться компонент с `displayName` равным `withFoo(Bar)`. > Почему? Свойство `displayName` может использоваться в инструментах разработчика или сообщениях об ошибках, и если оно ясно выражает связь между компонентами, это помогает понять, что происходит. ```jsx // плохо export default function withFoo(WrappedComponent) { return function WithFoo(props) { return <WrappedComponent {...props} foo />; } } // хорошо export default function withFoo(WrappedComponent) { function WithFoo(props) { return <WrappedComponent {...props} foo />; } const wrappedComponentName = WrappedComponent.displayName || WrappedComponent.name || 'Component'; WithFoo.displayName = `withFoo(${wrappedComponentName})`; return WithFoo; } ``` - **Названия свойств**: Избегайте использования названий свойств DOM-компонента для других целей. > Почему? Люди ожидают, что такие свойства как `style` и `className` имеют одно определённое значение. Изменение этого API в вашем приложении ухудшает читабельность и поддержку кода, что может приводить к ошибкам. ```jsx // плохо <MyComponent style="fancy" /> // плохо <MyComponent className="fancy" /> // хорошо <MyComponent variant="fancy" /> ``` ## <a name="declaration">Объявление</a> - Не используйте свойство `displayName` для именования компонентов. Вместо этого задавайте имя классу компонента. ```jsx // плохо export default React.createClass({ displayName: 'ReservationCard', // здесь начинается работа }); // хорошо export default class ReservationCard extends React.Component { } ``` ## <a name="alignment">Выравнивание</a> - Следуйте приведённым ниже стилям для JSX-синтаксиса. eslint: [`react/jsx-closing-bracket-location`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md) ```jsx // плохо <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // хорошо <Foo superLongParam="bar" anotherSuperLongParam="baz" /> // если свойства помещаются на одну строку, оставляйте их на одной строке <Foo bar="bar" /> // отступ у дочерних элементов задается как обычно <Foo superLongParam="bar" anotherSuperLongParam="baz" > <Quux /> </Foo> // плохо {showButton && <Button /> } // плохо { showButton && <Button /> } // хорошо {showButton && ( <Button /> )} // хорошо {showButton && <Button />} ``` ## <a name="quotes">Кавычки</a> - Всегда используйте двойные кавычки (`"`) для JSX-атрибутов, а одинарные кавычки (`'`) для всего остального JS. eslint: [`jsx-quotes`](https://eslint.org/docs/rules/jsx-quotes) > Почему? Для стандартных HTML-атрибутов обычно используются двойные кавычки, а не одинарные, и JSX-атрибуты тоже следуют этому соглашению. ```jsx // плохо <Foo bar='bar' /> // хорошо <Foo bar="bar" /> // плохо <Foo style={{ left: "20px" }} /> // хорошо <Foo style={{ left: '20px' }} /> ``` ## <a name="spacing">Пробелы</a> - Всегда вставляйте один пробел в ваш самозакрывающийся тег. eslint: [`no-multi-spaces`](https://eslint.org/docs/rules/no-multi-spaces), [`react/jsx-tag-spacing`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-tag-spacing.md) ```jsx // плохо <Foo/> // very bad <Foo /> // плохо <Foo /> // хорошо <Foo /> ``` - Не отделяйте фигурные скобки пробелами в JSX. eslint: [`react/jsx-curly-spacing`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-curly-spacing.md) ```jsx // плохо <Foo bar={ baz } /> // хорошо <Foo bar={baz} /> ``` ## <a name="props">Свойства (Props)</a> - Всегда используйте `camelCase` для названий свойств. ```jsx // плохо <Foo UserName="hello" phone_number={12345678} /> // хорошо <Foo userName="hello" phoneNumber={12345678} /> ``` - Не указывайте значение свойства, когда оно явно `true`. eslint: [`react/jsx-boolean-value`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-boolean-value.md) ```jsx // плохо <Foo hidden={true} /> // хорошо <Foo hidden /> // хорошо <Foo hidden /> ``` - Всегда добавляйте свойство `alt` для тегов `<img>`. Если изображение является презентационным, `alt` может быть пустой строкой или `<img>` обязан иметь `role="presentation"`. eslint: [`jsx-a11y/alt-text`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/alt-text.md) ```jsx // плохо <img src="hello.jpg" /> // хорошо <img src="hello.jpg" alt="Me waving hello" /> // хорошо <img src="hello.jpg" alt="" /> // хорошо <img src="hello.jpg" role="presentation" /> ``` - Не используйте такие слова как "изображение" ("image"), "фото" ("photo"), или "картинка" ("picture") в свойстве `alt` тега `<img>`. eslint: [`jsx-a11y/img-redundant-alt`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/img-redundant-alt.md) > Почему? Скринридеры уже сообщают что `img` элементы являются картинками, так что нет необходимости включать эту информацию в текст свойства `alt`. ```jsx // плохо <img src="hello.jpg" alt="Picture of me waving hello" /> // хорошо <img src="hello.jpg" alt="Me waving hello" /> ``` - Используйте только валидные, не абстрактные [ARIA роли](https://www.w3.org/TR/wai-aria/#usage_intro). eslint: [`jsx-a11y/aria-role`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/aria-role.md) ```jsx // плохо - не ARIA роль <div role="datepicker" /> // плохо - асбтрактная ARIA роль <div role="range" /> // хорошо <div role="button" /> ``` - Не используйте `accessKey` на элементах. eslint: [`jsx-a11y/no-access-key`](https://github.com/evcohen/eslint-plugin-jsx-a11y/blob/master/docs/rules/no-access-key.md) > Почему? Несоответствия между сочетанием комбинаций клавиш и командами с клавиатуры затрудняют доступ для людей, которые пользуются экранными считывателями и клавиатурами. ```jsx // плохо <div accessKey="h" /> // хорошо <div /> ``` - Не используйте индексы элементов массива в качестве свойства `key`. Отдавайте предпочтение уникальному ID. eslint: [`react/no-array-index-key`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-array-index-key.md) > Почему? Неиспользование стабильного ID [является антипаттерном](https://medium.com/@robinpokorny/index-as-a-key-is-an-anti-pattern-e0349aece318), потому что это может негативно повлиять на производительность компонента и вызвать проблемы с его состоянием. Мы не рекомендуем использовать индексы для ключей, если порядок элементов может измениться. ```jsx // плохо {todos.map((todo, index) => <Todo {...todo} key={index} /> )} // хорошо {todos.map(todo => ( <Todo {...todo} key={todo.id} /> ))} ``` - Всегда указывайте подробные `defaultProps` для всех свойств, которые не указаны как необходимые. > Почему? `propTypes` является способом документации, а предоставление `defaultProps` позволяет читателю вашего кода избежать множества неясностей. Кроме того, это может означать, что ваш код может пропустить определённые проверки типов. ```jsx // плохо function SFC({ foo, bar, children }) { return <div>{foo}{bar}{children}</div>; } SFC.propTypes = { foo: PropTypes.number.isRequired, bar: PropTypes.string, children: PropTypes.node, }; // хорошо function SFC({ foo, bar, children }) { return <div>{foo}{bar}{children}</div>; } SFC.propTypes = { foo: PropTypes.number.isRequired, bar: PropTypes.string, children: PropTypes.node, }; SFC.defaultProps = { bar: '', children: null, }; ``` - Используйте оператор расширения для свойств осознанно. > Почему? В противном случае вы скорее всего будете передавать внутрь компонента лишние свойства. А для React версии 15.6.1 и старше, вы можете [передать невалидные HTML-атрибуты в DOM](https://reactjs.org/blog/2017/09/08/dom-attributes-in-react-16.html). Исключения: - Компоненты высшего порядка, которые передают свойства внутрь дочернего компонента и поднимают propTypes. ```jsx function HOC(WrappedComponent) { return class Proxy extends React.Component { Proxy.propTypes = { text: PropTypes.string, isLoading: PropTypes.bool }; render() { return <WrappedComponent {...this.props} /> } } } ``` - Использование оператора расширения для известных, явно заданных свойств. Это может быть особенно полезно при тестировании компонентов React с конструкцией beforeEach из Mocha. ```jsx export default function Foo { const props = { text: '', isPublished: false } return <div {...props} />; } ``` Примечания по использованию: Если возможно, отфильтруйте ненужные свойства. Кроме того, используйте [prop-types-exact](https://www.npmjs.com/package/prop-types-exact), чтобы предотвратить ошибки. ```jsx // плохо render() { const { irrelevantProp, ...relevantProps } = this.props; return <WrappedComponent {...this.props} /> } // хорошо render() { const { irrelevantProp, ...relevantProps } = this.props; return <WrappedComponent {...relevantProps} /> } ``` ## <a name="refs">Ссылки (Refs)</a> - Всегда используйте функции обратного вызова. eslint: [`react/no-string-refs`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-string-refs.md) ```jsx // плохо <Foo ref="myRef" /> // хорошо <Foo ref={(ref) => { this.myRef = ref; }} /> ``` ## <a name="parentheses">Круглые скобки</a> - Оборачивайте в скобки JSX теги, когда они занимают больше одной строки. eslint: [`react/jsx-wrap-multilines`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-wrap-multilines.md) ```jsx // плохо render() { return <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent>; } // хорошо render() { return ( <MyComponent variant="long body" foo="bar"> <MyChild /> </MyComponent> ); } // хорошо, когда одна строка render() { const body = <div>hello</div>; return <MyComponent>{body}</MyComponent>; } ``` ## <a name="tags">Теги</a> - Всегда используйте самозакрывающиеся теги, если у элемента нет дочерних элементов. eslint: [`react/self-closing-comp`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/self-closing-comp.md) ```jsx // плохо <Foo variant="stuff"></Foo> // хорошо <Foo variant="stuff" /> ``` - Если ваш компонент имеет множество свойств, которые располагаются на нескольких строчках, то закрывайте тег на новой строке. eslint: [`react/jsx-closing-bracket-location`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-bracket-location.md) [`react/jsx-closing-tag-location`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-closing-tag-location.md) ```jsx // плохо <Foo bar="bar" baz="baz" /> // хорошо <Foo bar="bar" baz="baz" /> ``` ## <a name="methods">Методы</a> - Используйте стрелочные функции для замыкания локальных переменных. Это удобно, когда вам нужно передать дополнительные данные в обработчик событий. Однако, убедитесь, что они [не сильно вредят производительности](https://www.bignerdranch.com/blog/choosing-the-best-approach-for-react-event-handlers/). В частности, могут быть ненужные перерисовки каждый раз, когда они будут передаваться PureComponent. ```jsx function ItemList(props) { return ( <ul> {props.items.map((item, index) => ( <Item key={item.key} onClick={(event) => { doSomethingWith(event, item.name, index); }} /> ))} </ul> ); } ``` - Привязывайте обработчики событий для метода `render` в конструкторе. eslint: [`react/jsx-no-bind`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/jsx-no-bind.md) > Почему? Вызов `bind` в методе `render` создаёт новую функцию при каждой перерисовке. Не используйте стрелочные функции в полях класса, поскольку это делает их [сложными для тестирования и отладки, и может негативно повлиять на производительность](https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1). К тому же концептуально поля класса предназначены для данных, а не для логики. ```jsx // плохо class extends React.Component { onClickDiv() { // ... } render() { return <div onClick={this.onClickDiv.bind(this)} />; } } // очень плохо class extends React.Component { onClickDiv = () => { // ... } render() { return <div onClick={this.onClickDiv} /> } } // хорошо class extends React.Component { constructor(props) { super(props); this.onClickDiv = this.onClickDiv.bind(this); } onClickDiv() { // ... } render() { return <div onClick={this.onClickDiv} />; } } ``` - Не используйте префикс подчёркивания для именования внутренних методов React компонента. > Почему? Префиксы подчёркивания иногда используются в других языках как соглашение о приватности. Но, в отличие от этих языков, в Javascript нет нативной поддержки приватности, все публично. Независимо от ваших намерений, добавление префикса подчёркивания к вашим свойства не делает их приватными, и любые свойства (с подчёркиванием или без) должны рассматриваться как публичные. Смотрите вопросы [#1024](https://github.com/airbnb/javascript/issues/1024), и [#490](https://github.com/airbnb/javascript/issues/490) для более глубокого обсуждения. ```jsx // плохо React.createClass({ _onClickSubmit() { // ... }, // ... }); // хорошо class extends React.Component { onClickSubmit() { // ... } // ... } ``` - Всегда возвращайте значение в методах `render`. eslint: [`react/require-render-return`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/require-render-return.md) ```jsx // плохо render() { (<div />); } // хорошо render() { return (<div />); } ``` ## <a name="ordering">Последовательность</a> - Последовательность для `class extends React.Component`: 1. произвольные `static` методы 1. `constructor` 1. `getChildContext` 1. `componentWillMount` 1. `componentDidMount` 1. `componentWillReceiveProps` 1. `shouldComponentUpdate` 1. `componentWillUpdate` 1. `componentDidUpdate` 1. `componentWillUnmount` 1. *обработчики кликов или событий*, такие как `onClickSubmit()` или `onChangeDescription()` 1. *getter методы для `render`*, такие как `getSelectReason()` или `getFooterContent()` 1. *произвольные render методы*, такие как `renderNavigation()` или `renderProfilePicture()` 1. `render` - Как определять `propTypes`, `defaultProps`, `contextTypes`, и т.д. ```jsx import React from 'react'; import Proptypes from 'prop-types'; const propTypes = { id: PropTypes.number.isRequired, url: PropTypes.string.isRequired, text: PropTypes.string, }; const defaultProps = { text: 'Hello World', }; class Link extends React.Component { static methodsAreOk() { return true; } render() { return <a href={this.props.url} data-id={this.props.id}>{this.props.text}</a>; } } Link.propTypes = propTypes; Link.defaultProps = defaultProps; export default Link; ``` - Последовательность для `React.createClass`: eslint: [`react/sort-comp`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/sort-comp.md) 1. `displayName` 1. `propTypes` 1. `contextTypes` 1. `childContextTypes` 1. `mixins` 1. `statics` 1. `defaultProps` 1. `getDefaultProps` 1. `getInitialState` 1. `getChildContext` 1. `componentWillMount` 1. `componentDidMount` 1. `componentWillReceiveProps` 1. `shouldComponentUpdate` 1. `componentWillUpdate` 1. `componentDidUpdate` 1. `componentWillUnmount` 1. *обработчики кликов или событий*, такие как `onClickSubmit()` или `onChangeDescription()` 1. *getter методы для `render`*, такие как `getSelectReason()` или `getFooterContent()` 1. *произвольные render методы*, такие как `renderNavigation()` или `renderProfilePicture()` 1. `render` ## `isMounted` - Не используйте `isMounted`. eslint: [`react/no-is-mounted`](https://github.com/yannickcr/eslint-plugin-react/blob/master/docs/rules/no-is-mounted.md) > Почему? [`isMounted` — это антипаттерн][anti-pattern], который недоступен при использовании ES6 классов и который планируют официально признать устаревшим. [anti-pattern]: https://facebook.github.io/react/blog/2015/12/16/ismounted-antipattern.html **[⬆ к оглавлению](#Оглавление)**