Самые распространенные ошибки в вашем React коде, которые вы (возможно) делаете +20



Вдохновленный прочитанными статьями на медиуме, я решил написать свою статью и рассказать вам как можно избегать самых распространенных ошибок в вашем 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 + 1 });
    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 + 1 });
incrementCounter = () => this.setState({ counter: 1 + 1 });

Для того, что бы это избежать можно использовать setState callback который получает как аргумент прошлое состояние state:


incrementCounter = () => this.setState((prevState) => ({ counter: prevState.counter + 1 }));


(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 методе, вы получите бесконечный цикл ре-рендера. React начинает проверку на ре-рендер когда у компонента меняется state или props. Если вы поменяете state после того как компонент замаунтился в DOM или уже обновился, вы запустите проверку заново и заново и заново…
При обновлении стейта в componentDidMount вы можете вызвать ре-рендер компонента еще одни раз, так как функция вызывается один раз после маунтинга компонента в DOM.
Если у вас возникает необходимость обновлять данные именно после маунтинга компонента, я предлагаю использовать переменные класса:


class MyComponent extends React.Component {
   componentDidMount() {
      this.veryImportantDataThatCanBeStoredOnlyAfterMount = 'I'll be back!';
   }

   veryImportantDataThatCanBeStoredOnlyAfterMount = void 0;

    render() {
        return <div />
    }
}


(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.data.set('iUseImmutableStructure', true);
    this.setState(data: 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" />

Спасибо вам!


Это все, что я бы хотел рассказать вам в рамках этой статьи. Я буду очень вам признателен, если вы, мои читатели, оставите мне ваш отзыв или комментарий, и поделитесь в нем вашим мнением относительно проблем, которые я описал в этой статье. Так же, вы можете рассказать мне про мои и ваши ошибки в коде и про все, что посчитаете нужным рассказать. Спасибо вам еще раз!




К сожалению, не доступен сервер mySQL