Вдохновленный прочитанными статьями на медиуме, я решил написать свою статью и рассказать вам как можно избегать самых распространенных ошибок в вашем React приложении и для чего это нужно делать.
Весь код написан в ES6 стиле, поэтому, что бы повторить его вам нужно использовать Babel в вашем проекте (а еще есть такие кто его не использует?).
Я постарался объяснить как можно подробнее каждую ошибку, поэтому, моя статья больше ориентирована на молодых разработчиков, которые еще находятся в поиске новых знаний. Хотя, как мне кажется, и опытный разработчик может найти для себя пару интересных вещей в этой статье.
Если вам интересно, то добро пожаловать под кат.
В скобках перед каждым параграфом я оставил ссылку на eslint правило. Вы сможете позднее найти их в git и добавить в свой проект.
(react/forbid-component-props)
Первая распространенная ошибка это передача 'style' и 'className' как props в ваш компонент. Избегайте этого, так как вы добавите много сложности в ваши компоненты.
Вместо это можно использовать библиотеку 'classnames' и добавить интересные вариации в ваш компонент (если вы используете css классы):
const { hasError, hasBorder } = this.props;
const componentClasses = classnames({
'your-component-main-class': true,
'your-component-main-class_with-error': hasError,
'your-component-main-class_with-border': hasBorder,
});
(react/forbid-prop-types)
Следующая ошибка — не информативные propTypes. Не используйте PropTypes.any, PropTypes.array и PropTypes.object. Описывайте ваши props как можно подробнее. Это позволит вам оставить хорошую документацию на будущее, и вы (или другой разработчик) еще не раз скажите себе большое спасибо.
class MyComponent extends React.Component {
static propTypes = {
user: PropTypes.shape({
id: PropTypes.number,
name: PropTypes.string,
}),
policies: PropTypes.arrayOf(PropTypes.shape({
id: PropTypes.number,
type: PropTypes.string,
value: PropTypes.string,
}),
}
}
(react/forbid-foreign-prop-types)
Давайте продолжим с propTypes. Не используйте propTypes другого компонента:
import SomeComponent from './SomeComponent';
SomeComponent.propTypes;
Создайте файл в котором вы будете содержать в порядки ваши глобальные propTypes:
import { userShape, policiesArray } from '../common/global_prop_types';
Это поможет babel-plugin-transform-react-remove-prop-types убрать propTypes из продакшен кода и сделать ваше предложение чуточку легче.
(react/no-access-state-in-setstate)
Следующая ошибка очень интересная:
class MyComponent extends React.Component {
state = {
counter: 1,
};
incrementCounter = () => this.setState({ counter: this.state.counter++ });
massIncrement = () => {
// this code will lead to not what you expect
this.incrementCounter();
this.incrementCounter();
}
}
Потому что setState это асинхронная функция состояния state в обоих случаях будет одинаковым.
this.state.counter будет равен 1 и мы получим:
incrementCounter = () => this.setState({ counter: 1++ });
incrementCounter = () => this.setState({ counter: 1++ });
Для того, что бы это избежать можно использовать setState callback который получает как аргумент прошлое состояние state:
incrementCounter = () => this.setState((prevState) => ({ counter: prevState.counter++ }));
(react/no-array-index-key)
Эта ошибка так же очень часто встречается, поэтому, прочтите внимательно и избегайте ее в будущем:
users.map((user, index) => (
<UserComponent {...user} key={index} />
));
React использует prop key как ссылку к DOM элементу, и это помогает ему быстро найти и отрендерить нужный компонент (все, конечно, сложнее, но я упростил специально).
Что случиться если вы добавите нового юзера в середину массива? React будет вынужден перерендерить все UserComponents после добавленного нового, так как индекс будет изменен для большого кол-ва компонентов. Используйте уникальные ключи вместо этого. Очень простой выход это id, которые вы получаете из вашей базы данных:
users.map((user) => (
<UserComponent {...user} key={user.id} />
));
(react/no-did-mount-set-state), (react/no-did-update-set-state)
Эта ошибка так же очень часто встречается в моей практике. Если вы попытаетесь обновить state в componentDidUpdate или componentDidMount методах, вы получите бесконечный цикл ре-рендера. React начинает проверку на ре-рендер когда у компонента меняется state или props. Если вы поменяете state после того как компонент замаунтился в DOM или уже обновился, вы запустите проверку заново и заново и заново...
(react/no-direct-mutation-state)
Мутация state это очень большая ошибка. Неконтролируемая мутация state приведет к необнаруживаемым багам и, как следствие, к большим проблемам. Мое персональное мнение это использование immutable-js, как библиотеку, которая добавляет иммутабельные структуры. И их вы можете использовать с Redux/MobX/Любой библиотекой state менеджмента. Так же вы можете использовать deepClone из lodash для клонирования state и последующей мутации клона или использовать новую фичу JS — деструкцию (destructuring):
updateStateWrong = () => this.state.imRambo = true;
updateStateRight = () => {
const clonedState = cloneDeep(this.state);
clonedState.imAGoodMan = true;
this.setState(clonedState);
}
updateWithImmutabelJS = () => {
const newState = this.state.set('iUseImmutableStructure', true);
this.setState(newState);
}
updateWithDestructuring = () => this.setState({ ...this.state, iUseDestructuring: true });
(react/prefer-stateless-function)
Данное правило описывает больше улучшение вашего кода и приложения, чем ошибку, но я, все же, рекомендую следовать этому правилу. Если ваш компонент не использует state, сделайте его stateless компонентом (мне больше нравиться термин 'pure component'):
class MyComponentWithoutState extends React.Component {
render() {
return <div>I like to write a lot of unneeded code</div>
}
}
const MyPureComponent = (props) => <div>Less code === less support</div>
(react/prop-types)
Пожалуйста, всегда добавляйте проверку на типы props (propTypes) если ваш компонент получает props. Думайте об этом как о документировании вашего кода. Вы еще не раз скажете себе 'спасибо' за это (а может быть и мне :)). PropTypes поможет вам понять и разобраться, что ваш компонент может отрендерить, а так же, что ему нужно для рендеринга.
MyPureComponent.propTypes = {
id: PropTypes.number.isRequired, // And I know that without id component will not render at all, and this is good.
}
(react/jsx-no-bind)
Очень распространенная и большая ошибка которую я видел в коде много раз. Не Используйте Bind И Arrow Function В Jsx. Никогда. Больше.
Самое жаркое место в аду ждет того кто пишет .bind(this) в JSX.
Каждый раз когда компонент рендериться ваша функция будет создаваться заново, и это может сильно затормозить ваше приложение (это связано с тем, что garbage collector будет вынужден запускаться значительно чаще). Вместо .bind(this) вы можете использовать Arrow functions определенным образом:
class RightWayToCallFunctionsInRender extends React.Component {
handleDivClick = (event) => event;
render() {
return <div onClick={this.handleDivClick} />
}
}
const handleDivClick = (event) => event;
const AndInPureComponent = () => {
return <div onClick={handleDivClick} />
}
(react/jsx-no-target-blank)
Ошибка связанная с безопасностью. Для меня выглядит очень странно, что люди до сих пор делают эту ошибку. Очень много людей написало очень много статей на эту тему в 2017.
Если вы создаете ссылку с target='_blank' атрибутом не забудьте добавить к ней rel='noreferrer noopener'. Очень просто:
<a href="https://example.com" target="_blank" rel="noreferrer noopener" />
Спасибо вам!
Это все, что я бы хотел рассказать вам в рамках этой статьи. Я буду очень вам признателен, если вы, мои читатели, оставите мне ваш отзыв или комментарий, и поделитесь в нем вашим мнением относительно проблем, которые я описал в этой статье. Так же, вы можете рассказать мне про мои и ваши ошибки в коде и про все, что посчитаете нужным рассказать. Спасибо вам еще раз!
Автор: MordorReal