Самые распространенные ошибки в вашем 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" />

Спасибо вам!


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

Вы можете помочь и перевести немного средств на развитие сайта



Комментарии (132):

  1. Fen1kz
    /#18859673

    Спасибо за статью!


    Пара вопросов:


    1. (react/no-direct-mutation-state)
      updateWithImmutabelJS = () => {
      const newState = this.state.set('iUseImmutableStructure', true);
      this.setState(newState);
      }

    Раньше вроде нельзя было использовать immutable-js как стейт, теперь можно?


    1. (react/jsx-no-bind)
      Очень распространенная и большая ошибка которую я видел в коде много раз. Не Используйте Bind И Arrow Function В Jsx. Никогда. Больше.
      Самое жаркое место в аду ждет того кто пишет .bind(this) в JSX.

    Вот это очень обидно щас было.


    Во-первых https://reactjs.org/docs/handling-events.html, Ctrl+F: bind(this
    Во-вторых такие функции не остаются в прототипе
    В-третьих подробнее https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1


    Я как бы только за не бойлерплейтить .bind, но хотелось бы обоснований =(

    • dagen
      /#18859845

      "Нативный" .bind при каждом вызове возвращает новую функцию, такова его природа. То же и со стрелочными функциями. Каждый новый вызов функций, в которые транспилируется jsx, приводит к новой функции в обоих случаях, что означает новую ссылку. Т.е. для пользовательских компонентов (с большой буквы) мы получаем как минимум лишнюю реконсиляцию. А для пользовательских и для встроенных компонентов (с маленькой буквы) — гарантированное создание новой функции при каждом рендере. Что приводит либо к повышению нагрузке при GC, либо к утечкам памяти.

      • Fen1kz
        /#18859923

        Эээ, .bind обычно делают в конструкторе ```this.handleClick = this.handleClick.bind(this)```

        Ну и я к тому, что заявлять «должны гореть в аду» можно за «with {}», «goto», «jQuery» (:trollface:), но вот про .bind vs class properties это чересчур.

        • staticlab
          /#18860021

          Я думаю, что автор имел в виду не bind в конструкторе, а bind непосредственно в обработчике события:


          <button onClick={ this.handleButtonClick.bind(this) }>

          • MordorReal
            /#18860233

            Спасибо. Я имел в виду именно этот вызов bind. Хотя в других местах я его тоже не очень люблю. По большей части из-за проблемы описанной dagen чуть выше.

            • Akuma
              /#18861197

              Интересный момент. Раньше и не задумывался как-то, хотя не думаю, что это сильно скажется на скорости или памяти в обычных приложениях.

              А как в этом случае передавать параметры?

              <button onClick={this.deleteRow.bind(this, id)}>Delete Row</button>

              • staticlab
                /#18861211

                В документации к react/jsx-no-bind предлагают делать подкомпонентами.

                • dagen
                  /#18864821

                  Akuma TL;DR: мало кнопок — оставить так, много кнопок — обернуть в функциональные компоненты.

                  Конкретно в этом случае (с button) не будет перерисовок (Реакт вынужден вызывать лишние реконсиляции только для пользовательских компонентов). Поэтому если у вас не так уж и много строк с кнопками, то самым выгодным вариантом будет так и оставить. Если много — то как ниже советует faiwer — обернуть в компоненты.

                  Только надо учесть, что выгода от компонентов будет только если используются stateless functional компоненты, и при этом нет observer от mobx-react (и других подобных вещей, напр. из recompose). Декоратор observer превращает ваши функциональные компоненты в обычные class-based, что означает, что вы променяете кучу функций, создающихся на каждый рендер, на кучу инстансов компонентов, по одному для каждой кнопки. А раз мы рассматриваем это только при варианте, когда кнопок много, то это будет расточительно.

                  • faiwer
                    /#18865085

                    Конкретно в этом случае (с button) не будет перерисовок

                    Будет removeEventListener и addEventListener. Просто потому что react понятия не имеет о том, что это по факту один и тот же метод. reflow из-за этого не будет, да, это просто eventListener. Согласен, что по факту это ерунда и такие вещи в браузерах должны быть очень заоптимизированы. И тут, наверное, лучше писать как удобнее и проще работать с кодовой базой, нежели считать такты ЦП.

                    • justboris
                      /#18865339 / +2

                      React же вроде использует делегацию событий. То есть вешается один слушатель на корневой элемент, а перерисовка просто заменяет функции во внутренних маппингах.


                      UPD. Проверил в CodeSanbox, так и есть

                      • faiwer
                        /#18865397

                        Кстати да, спасибо, что напомнили. Плюс там ещё Proxy вместо настоящих Events.

              • faiwer
                /#18861239

                <DeleteRowButton {...{ id }}/> // 1
                <DeleteRowButton {...{ id }} delete={this.deleteRow}/> // 2

                • Akuma
                  /#18861325 / +1

                  А кто-нибудь вымерял улучшение производительности?
                  Мне кажется это как echo «string»; и echo 'string'; в PHP — экономия на спичках.

                  • faiwer
                    /#18861383

                    Тут всё сугубо зависит от вложенности. Если речь об единичном теге <button/> то тут речь скорее про общий порядок и приверженность одному подходу вдоль всего приложения. А если речь идёт о чём-то большом, то там и тормоза будут уже ощутимые визуально. Вот вам вырожденный пример.


                    • Имеем некий <MyTable/> в котором 200 <MyTableRow/> и в них по 200 <MyTableCell/>
                    • Пишем всё это ногами, без PureComputed, без bind и пр.
                    • В <MyTable/> передаём onClick={this.onClick.bind(this, 42)}
                    • Получаем, что при каждом рендере компонента, в котором есть <MyTable/> мы заставляем React рендерить вообще всю таблицу всегда

                    На самом деле ситуация надуманна, т.к. если программист будет писать в таком стиле, он убьётся о тормоза ещё в самом начале и начнёт думать головой. И да, будут у него и bind-ы и PureComputed-ы и прочие ужасырадости аля functional way.


                    Так что всё зависит от конкретных задач. Чем крупнее и сложнее проект, тем раньше эти вещи станут очевидными. И да, с тормозами можно и не столкнуться, пока проект это просто набор простых формочек.


                    В целом React экосистема и его подходы поощряют создание множества мелких компонент, вместо больших и сложных. В том числе и вынос одно-теговых компонент. Но тут на вкус и цвет...

                    • Akuma
                      /#18861403

                      Ну… да, пример конечно очень надуманный :)
                      Как представится возможность, хочу все же на реальном приложении протестировать улучшения. Если вдруг не забуду, отпишусь.
                      А то такие примеры из разряда «я залез на Эверест, прострелил себе колено и не могу слезть. Виноват React».

                      • faiwer
                        /#18861421

                        я залез на Эверест, прострелил себе колено и не могу слезть. Виноват React

                        Беда в том что правда лезут и простреливают. Массово. Тут коллега жаловался недавно, что у него инициализация проекта (куда его пригласили) 45 секунд в браузере занимает. На каждый чих по 3-4 HoC-а. Про reselect никто никогда не слышал. Про React way видимо тоже. Наверное "преждевременная оптимизация корень всех зол" было их лозунгом, а потом legacy накопилось и стало "тут уже ничего не исправить, Господь — жги".

              • Xu4
                /#18865237 / +1

                У меня некоторое время довольно-таки хорошо работала такая конструкция (пишу без предварительного тестирования — по памяти, так что код может быть нерабочий, но, главное, из него будет понятен смысл):

                class MyRows extends React.Component {
                    constructor(props) {
                        super(props);
                
                        this.state = {
                            rows: props.rows,
                        };
                    }
                
                    /**
                     * @param {SyntheticEvent} event
                     */
                    handleDelete = event => {
                        let rows = Object.assign({}, this.state.rows);
                
                        delete rows[event.target.dataset.rowId];
                
                        this.setState({rows});
                    }
                
                    render() {
                        return <div>
                            {Object.values(this.state.rows).map(row => {
                                return <button
                                    key={row.id}
                                    onClick={this.handleDelete}
                                    data-row-id={row.id}
                                >Delete row #{row.id}</button>;
                            })}
                        </div>;
                    }
                }
                


                Суть в том, что я пишу параметры в нативный для DOM dataset конкретной кнопки, и потом в хэндлере обращаюсь к этому дата-сету за данными. При таком подходе не приходится делать бинд каждый раз, когда происходит ре-рендер.

            • codefln
              /#18866117

              То есть вы предлагаете выносить каждую стрелочную функцию за пределы компонента? Правильно?

              • MordorReal
                /#18866155

                Нет, конечно. В обычном компоненте вы можете создать функцию внутри него и вызывать ее this.functionName. В случае stateless компонента функцию можно вынести наружу, иначе она будет создаваться каждый раз заново при перерендере компонента.

        • dagen
          /#18864731

          Эээ, .bind обычно делают в конструкторе ```this.handleClick = this.handleClick.bind(this)```

          Конечно если делать бинд, то в конструкторе (а лучше вообще не делать так, чтобы нужно было использовать бинд для обработчиков). Я отвечал на ваш вопрос, в котором вы процитировали автора:
          Не Используйте Bind И Arrow Function В Jsx. Никогда. Больше.

      • VolCh
        /#18860159 / +2

        Запрет на использование в свойствах биндинга или стрелочных функциях выглядит как преждевременная оптимизация.

    • MordorReal
      /#18860273

      https://github.com/facebook/immutable-js/wiki/Immutable-as-React-state
      По поводу state вы правы. Здесь будет верно:


      const newState = this.state.data.set('iUseImmutableStructure', true);

      К сожалению, документация по Реакту уже довольно давно не поспевает, так сказать, за прогрессом и некоторые моменты там немного устарели. Про bind чуть ниже написано пару хороших комментариев. Рекомендую почитать. По поводу прототипирования в реакте первый раз услышал от вас. Используйте композицию вместо наследования (вольный перевод высказывания Gang of Four).
      Не нашел в статье под пунктом три ничего критического для себя, что бы заставило меня не использовать стрелочные функции в компонентах. Ключевое слово static безусловно полезно. Я обычно использую его для propTypes внутри компонента, как и описывал автор.

    • faiwer
      /#18860707

      В-третьих подробнее https://medium.com/@charpeni/arrow-functions-in-class-properties-might-not-be-as-great-as-we-think-3b3551c440b1

      белки-истерички.jpg. Других ассоциаций поле прочтения этой статьи у меня нет. Да, => не серебряная пуля. Своего рода костыль. Но реально столкнуться с такой проблемой отсутствия метода в прототипе можно только начав что-то наследовать (довольно редкое явление в React-экосистеме). Там, если захочется сахара, нас могут выручить те же декораторы.


      Всё что там написано про производительность, имхо, вообще та ещё муть. Даже на довольно больших проектах она незаметна. На фоне всех аллокаций наши лишние => просто теряются.


      Мне кажется в большинстве случаев (99.9+%) использование => для решения проблемы контекста более, чем оправдано.

  2. justboris
    /#18860111 / +1

    (react/no-did-mount-set-state)

    Официальная документация считает иначе. В componentDidMount можно вызывать setState:


    You may call setState() immediately in componentDidMount().

    Про componentDidUpdate верно, можно устроить бесконечный цикл, но did mount тут не при чем

    • funca
      /#18860175

      все так. здесь слово «may» это «можно, если без этого ну вообще ни как». по идее, react/no-did-mount-set-state ловит лишь те ситуации, когда setState вызывается синхронно из componentDidMount. здесь синхронный вызов хоть и не вгоняет компонент в бесконечный цикл, но все равно приводит к повторному вызову render (о чем документация тоже предупреждает). асинхронно вызывать ни кто не запрещает.

      airbnb недавно выключили проверку из-за server-side rendering: github.com/airbnb/javascript/issues/684#issuecomment-355625957

      • MordorReal
        /#18860235

        Спасибо за дополнение. При изменении стейта componentDidMount можно вызвать перерендер компонента еще один раз. Если вам нужно поменять стейт после перерендера, возможно, стоит вынести эту переменную как переменную класса:


        class MyComponent extends React.Component {
          state = {
            varThatAffectRender: true,
          }
        
          varThatNotAffectRender = true;
        }

        • justboris
          /#18860283

          А как быть с таким примером:


          componentDidMount() {
             this.loadData();
             this.setState({loading: true});
          }
          
          render() {
            if(this.state.loading) {
               return <Loader />
            }
            //...
          }

          Здесь переменная класса не сработает, нужно лоадер показать.

          • MordorReal
            /#18860287

            В данном случае вы можете выставить loading в true в state, или перенести его в пропсы и выставить static defaultProps = { loading: true };

            • justboris
              /#18860319

              > В данном случае вы можете выставить loading в true в state

              То есть, вы имеете в виду все-таки вызвать setState?

              • MordorReal
                /#18860333

                Нет. Я имею ввиду:


                class MyComponent extends React.Component {
                  state = {
                    loading: true,
                  }
                
                  render() {
                    const { loading } = this.state;
                    if(loading) {
                       return <Loader />;
                    }
                    //....
                }

                • justboris
                  /#18860351

                  а если загрузка в componentDidMount происходит не всегда, а по какому-то условию? Придется это условие дублировать в инициализаторе.

                  А все ради сомнительной экономии на спичках.

                  • MordorReal
                    /#18860357

                    Вы просто это условие переносите в инициализацию стейта или конструктор если вам нужны пропсы.
                    Дублировать как раз ничего и не нужно будет в этом случае.
                    Если компонент очень тяжелый (например, таблица с 5к строками) и рендериться очень много времени, вы сможете сэкономить 50% времени от начального рендеринга, а это может быть очень не мало.

                    • justboris
                      /#18860455

                      Откуда цифра в 50%? В документации про componentDidMount сказано, что в браузер выведется только результат второго рендера, после вызова setState. Максимум, что вызовется лишний раз, это метод render, что не так уж много.


                      Я тут провел свое исследование, отрендерил таблицу на 5к строк (вот код):



                      Какой-то особой разницы для пользователя не видно, что там, что там страница готова примерно через 1,5 секунды.


                      Если и искать способы ускорить свое React-приложение, то точно не в этом setState.

                      • MordorReal
                        /#18860475

                        Поменяйте в setState массив строк который вы передаете в рендер компонента и увидете разницу.

                        • justboris
                          /#18860519

                          Вроде изначально разговор шел о том, чтобы поменять значение флага `loading`, а не набор элементов целиком…

                          Конечно, если постараться, можно заставить компонент тормозить, но от небольших изменений стейта ничего не испортится.

                          • MordorReal
                            /#18860685

                            Проблема про которую я говорю в статье описывает ситуацию изменения стейта в целом. Не важно насколько они значительные или нет. Чем они больше тем тормознее будет ваш компонент. Но негативный эффект все равно будет. Не важно, что при изменении всего одной переменной он не значительный, он есть и это факт. При изменении массива в 5к объектов, который вы положили в стейт, вы получите еще больший негативный эффект.

                            • justboris
                              /#18860807 / +1

                              Основная беда подобных статей то, что там пишется «не используйте X, он медленный» без объяснения при каких условиях и почему.
                              В результате неопытные пользователи начинают заниматься такими «оптимизациями», ухудшая читаемость кода и порождая баги.

                              Как написали ниже другие комментаторы — преждевременная оптимизация — не ок. Советы типа react/no-did-mount-set-state больше похожи на карго-культ, реальная оптимизация делается по-другому

          • funca
            /#18860367

            сначала вызывается render, а потом componentDidMount. чтобы ваш пример сработал как ожидается при первой отрисовке компонента, state.loading должен быть проинициализирован true где-то раньше (как вариант — в конструкторе). поэтому в целом такая конструкция с setState только за зря дергает render еще раз.

      • justboris
        /#18860275

        Это один из тех случаев, где можно перемудрить с преждевременной оптимизацией. Если какая-то задача решается в один вызов setState, то лучше так и сделать, чем изобретать альтернативное решение (и не факт, что более производительное).

  3. justboris
    /#18860115 / +1

    (react/forbid-prop-types)
    Описывайте ваши props как можно подробнее.

    Если уж у вас есть желание описать свойства подробнее, то лучше заморочиться с TypeScript/Flow. С ними и переиспользуемость кода лучше, и в текстовых редакторах лучше поддержка.

    • funca
      /#18860191

      TypeScript делает статический анализ на этапе компиляции, а PropTypes — на этапе выполнения. тут нельзя однозначно сказать, что лучше. это очень старая история.

    • AmdY
      /#18860201

      Последний раз когда писал на react + flow, то в IDE phpstrom не работал автокомплит, приходилось дублировать через propTypes

      • k12th
        /#18860539 / +1

        Потому что flow, как ни странно, не про автокомплит, а про проверку корректности программы на основе информации о типах.
        В TypeScript с этим веселее, и корректность на лету проверяется (хотя в теории система типов у flow несколько строже), и автокомплит весьма приятный, и web/phpstorm с ним лучше дружат.

        • AmdY
          /#18860697

          Я понимаю, что такое flow. В комментарии выше было про «переиспользуемость кода лучше, и в текстовых редакторах лучше поддержка», вот я и написал про автокомплит.
          К тому же flow для статического анализа типов, а propstypes делает это рантайм. Так что лучше использовать оба инструмента.

          p.s. А как давно typescript рантайм проверяет типы? Раньше он так делать не умел и приходилось использовать дополнительный пакет.

          • k12th
            /#18860967 / +1

            TypeScript и не проверяет в рантайме. Но, рассуждая логически, откуда в рантайме может прилететь неверный тип? Либо мы где-то поленились и написали any — ССЗБ. Либо из внешних источников пришло что-то неожиданное (с сервера или там из localStorage). Тут propTypes поможет, но лучше было бы осуществлять такую проверку там где эти данные пришли, а не на стадии отображения.

    • acsent1
      /#18861883

      Поддержка flow в том же vscode просто ужасная (отдельный плагин).
      Постоянно виснет, плодит процессы flow.exe. Автокомплит от flow работает через раз, а родной приходится отключать.
      Может конечно у фэйсбука есть какая-то своя IDE?

      • justboris
        /#18863347

        Ну, значит, пользуйтесь TypeScript. Я с ним работаю и в WebStorm и в VS Code, все нормально.


        Flow я просто упомянул как еще одну альтернативу.

  4. kv4sha
    /#18860211 / +1

    (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>
    



    А ничего что stateless и pure components это совсем разные понятия?
    И что функциональные компоненты не являются чистыми?
    И использование их без тулов типа recompose просадит производительность?

    • MordorReal
      /#18860249

      Поискал сейчас специально, но к сожалению, не нашел официального определения stateless компонентов и pure components. Если попытаться перевести дословно, то stateless значит без стейта, без состояния, что в целом верно описывает компонент. pure component же можно попробовать перевести как чистый компонент, что подразумевает отсылку к чистым функциям из функционального программирования.
      Что же такое чистая функция? Wiki
      Вики говорит, что фунция чистая если:


      1. является детерминированной;
      2. не обладает побочными эффектами;

      В нашем случае компонент всегда будет возвращать один и тот же результат, на одни и теже входящие данные — то есть он является детерменированным. А так же не обладает побочными эффектами.
      Суммирую, думаю, что мы можем назвать данный компонент чистым.

      • kv4sha
        /#18860337

        pure component же можно попробовать перевести как чистый компонент, что подразумевает отсылку к чистым функциям из функционального программирования.

        Это ж в какой вселенной наличие слова чистый подразумевает отсылку к чистым функциям?
        Если у меня есть чистая тарелка то что она должна быть детерминирована?

        В нашем случае компонент всегда будет возвращать один и тот же результат, на одни и теже входящие данные — то есть он является детерменированным. А так же не обладает побочными эффектами.
        Суммирую, думаю, что мы можем назвать данный компонент чистым.

        Суммируя мы можем назвать этот компонент чистой функцией.

        Этот компонент будет pure function и stateless component, но не pure component.
        И в библиотеке recompose есть HOC который называется pure и делает компонент pure, что как бы наталкивает на мысль, что обычный функциональный компонент не является pure.

        • MordorReal
          /#18860343

          Это ж в какой вселенной наличие слова чистый подразумевает отсылку к чистым функциям?
          Если у меня есть чистая тарелка то что она должна быть детерминирована?

          Простите, но не совсем понял при чем здесь чистая тарелка. Я пытаюсь объяснить, что именно я подразумеваю под понятием PureComponent. Это лично мое определение.
          И в библиотеке recompose есть HOC который называется pure и делает компонент pure, что как бы наталкивает на мысль, что обычный функциональный компонент не является pure.

          Я не использовал библиотеку recompose в продакшене, но читал пару статей про нее. Не понимаю, при чем здесь данная библиотека и мое определение чистого компонента.

          • kv4sha
            /#18860359

            Простите, но не совсем понял при чем здесь чистая тарелка.

            А я не совсем понимаю при чём здесь чистая функция?
            что подразумевает отсылку к чистым функциям из функционального программирования.

            Каким образом?

            Компонент чистый, когда при одних и тех же пропсах не вызывается рендер, и соответственно не происходит просчёт всех компонентов которые находятся ниже.

            • dy-ya
              /#18860495

              А можно по подробнее почему не стоит использовать stateless components без recompose? И почему они просаживают производительность?

              • kv4sha
                /#18860977

                import React from 'react';
                
                // любой список или таблица с большим кол-вом данных 10к например
                const Table = ({ data }) => (
                    <table>
                        {data.map(() => {
                            // tr td what ever
                        })}
                    </table>
                );
                
                const Wrapper = ({ searchString, onSearchStringChange, tableData }) => (
                    <div>
                        <input value={searchString} onChange={onSearchStringChange} />
                
                        {/* любое изменение в input будет вызывать пересчёт Table */}
                        <Table data={tableData} />
                    </div>
                );
                

                И выйдет так что пользователь вводит слово в input, а он лагает потому что js поток пытается выполнить data.map() и занимает это дочерта.
                А если Table чистый компонент, то он не будет даже data.map выполнять потому что массив data не изменился.

                А если у нас внутри Table ещё что-то тяжёлое, ещё больше нагрузка и больше лагов.

                • dy-ya
                  /#18861001

                  При таком раскладе само собой. Но все надо делать с умом) из Вашего комментария, я так понял что Вы имеете в виду вообще отказаться от их использования.

                  • kv4sha
                    /#18861085 / +1

                    Мне проще всё писать в одном стиле и сразу чистым, и если уж возникает необходимость убирать pure. Основная мысль — дефолтно всё pure.

                    • dy-ya
                      /#18861133

                      Если бы это работало лучше, каждый компонент в реакте был бы pure по дефолту. Достаточно часто операция поверхностного сравнения для тупого компонента будет дороже чем выполнение рендера самого компонента, особенно если мы говорим о мелких атомарных компонентах, которые отвечают за рендер небольшого участка дома. Поэтому, но мой взгляд это не совсем верно. Это как прописывать shouldComponentUpdate в каждом компоненте (до появления pureConponent), вряд ли Вы так делали.

                      • faiwer
                        /#18861153

                        каждый компонент в реакте был бы pure по дефолту

                        Я полагаю, что они давно кусают локти, что не сделали так с самого начала. А теперь уже поздно.


                        Достаточно часто операция поверхностного сравнения для тупого компонента будет дороже чем выполнение рендера самого компонента

                        Как вы себе это представляете? Нет серьёзно. Вот есть у нас типовой объект props с 5 полями. Итого у нас 6 ===. И вот есть у нас типовой рендер с 6-8 тегами. Это аллокация целой кучи объектов, а затем ещё реконсиляция. И всё впустую. Мне кажется эти вещи по времени выполнения вообще не сопоставимы.


                        Я вижу только 1 случай, когда 2-е может перебить 1-е. Это когда наш компонент в 80+% случаев и правда требует re-render-а, т.к. там что-то меняется. Т.е. чуть ли не каждый первый render в нём приведёт к реальным DOM-операциям. И тогда и правда у нас проверка почти бесполезна. Но даже в этом случае на общем фоне выполняемых задач она почти невесомая.

                        • dy-ya
                          /#18861449

                          Честно говоря чтоб сказать однозначно — надо потестить, мне как-то никогда в голову не приходило (либо не было надобности) использовать PureComponent на каждом компоненте, если есть эта проверка на тех компонентах, которые, допустим, рендерят большие списки, то проверять еще каждый элемент этого списка — помоему лишняя операция сравнения в процессе апдейта. В случае если вы на 80+% уверены что компонент не будет ререндериться, тогда уж проще просто вернуть false из shouldComponentUpdate. К тому же не забывайте о том что при использовании PureComponent, если компонент все-таки будет обновляться — будут вызываться все lifecycle hooks компонента, которые Вам (возможно) в данном случае не нужны.

                          Вот нашел твит Абрамова на этот счет
                          twitter.com/dan_abramov/status/759383530120110080

                          • faiwer
                            /#18861497

                            В случае если вы на 80+% уверены что компонент не будет ререндериться, тогда уж проще просто вернуть false из shouldComponentUpdate

                            Не понял, что вы этим хотели сказать? .forceUpdate вызывать? оО


                            К тому же не забывайте о том что при использовании PureComponent, если компонент все-таки будет обновляться — будут вызываться все lifecycle hooks компонента

                            fix Хм. Не сразу правильно прочёл. Поясните, плз, что вы этим хотели сказать. Тут что PureComponent, что просто Component, одинаковое поведение. Не понял мысль.


                            Касательно твита. Не нашёл там ничего умнее, чем:


                            Basically compare + render anyway is slower than just render anyway

                            Какое откровение?! :) Я комментом выше об этом и написал. Впрочем от него я и не ожидал, чего-то умного. Что вы с ним носитесь, как с писанной торбой (не именно вы, а вообще сообщество).

                          • faiwer
                            /#18861513

                            Кажется я понял. Вы имеете ввиду, что если использовать вместо PureComponent не Component, а просто функцию (ака stateless notation)? А разве это не упрощённая запись Component? Разве там предусмотрена для них какая-то другая логика lifecycle? Я в код не лез, но думаю, что алгоритм един, это лишь вопрос сахара.

                            • dy-ya
                              /#18861555

                              Кажется я понял. Вы имеете ввиду, что если использовать вместо PureComponent не Component, а просто функцию (ака stateless notation)?

                              Верно, комментарий изначально вроде был именно о них.

                              А разве это не упрощённая запись Component?

                              На сколько я знаю, это одна из «фичей» функциональных компонентов, что они опускают всякую всячину вроде хуков. По крайней мере сравнение транспиленного объявления этих компонентов говорит о наличии какой-то разницы)
                              var A = function (_React$Component) {
                                 _inherits(A, _React$Component);
                              
                                 function A() {
                                    _classCallCheck(this, A);
                              
                                    return _possibleConstructorReturn(this, (A.__proto__ || Object.getPrototypeOf(A)).apply(this, arguments));
                                 }
                              
                                 _createClass(A, [{
                                    key: "render",
                                    value: function render() {
                                       return React.createElement("div", null);
                                    }
                                 }]);
                              
                                 return A;
                              }(React.Component);
                              
                              var B = function B() {
                                 return React.createElement("div", null);
                              };

                              • faiwer
                                /#18861567

                                В трансплайенном коде вы ничего и не увидите (он вообще ни о каком React ничего не знает же). Тут надо смотреть что происходит в недрах обработки VDom древа. Я думаю, что там поведение идентичное обычному пустому class-Component-у. Очень сомневаюсь что там будет какая-нибудь статистически непренебрежимая разница. Это уже даже не экономия на списках, а на атомах в этой спичке. Сродне ++ i >> i ++ :)


                                Впрочем, было бы любопытно послушать тех, кто такие замеры проводил или ковырялся с fiber-исходниках.

                                • dy-ya
                                  /#18861589

                                  Профит от как минимум меньшего количества кода, вроде как тоже не последнее дело в общем итоге.
                                  Практически все статьи, что мне встречались по этому поводу советуют все-таки «разумно» использовать PureComponent. В любом случае ради интереса было бы интересно увидеть на практике в сравнении рендер двух больших списков в разных вариациях.

                                  • kv4sha
                                    /#18862259

                                    Статья для 14 реакта, и там видна разница между Component и functional Component она 6-7%

                                    Я взял код оттуда и зделал 16 реакт, тесты показали тот же прирост всего в 6-7%

                                    А вот простой вызов функционального компонента как функции даёт прирост в 60%, что наталкивает на мысль, что там далеко не простой вызов функции, и логики там даже на функциональные компоненты накидывается дочерта.

                                    И никто не мешает мемоизировать функциональный компонент))

                                    • faiwer
                                      /#18862291

                                      6-7% не так уж и мало, на самом деле, я думал будет в р-не 1-2%. Спасибо за ссылку. Почитаю.

                                      • faiwer
                                        /#18862353

                                        Хм:


                                        There is no “optimized” support for them yet because stateless component is wrapped in a class internally. It's same code path.

                                        как я и думал, но


                                        This pattern is designed to encourage the creation of these simple components that should comprise large portions of your apps. In the future, we’ll also be able to make performance optimizations specific to these components by avoiding unnecessary checks and memory allocations.

                                        Парни решили этого не ждать, и просто забить на JSX. Все функциональные компоненты сразу вызывать как MyComponent(innerProps) и получили 45% производительности, избежав всей React машинерии.


                                        Т.е. потенциально тут может быть существенный прирост в будущем (45%).


                                        Отдельный правда вопрос — а дала ли им эта экономия на спичках какой-либо существенный результат всецело (с учётом всех факторов, таких как DOM)? Но такое померить сложнее.


                                        Ну и отдельный момент, в сравнении с PureComputed. Тут можно отыграть (на shouldComponentUpdate) куда больше этих 45%. Как минимум расставив их в тех местах, где VDom-а много (уж очень недёшего его вглубь проверять, там ведь не shallow).

          • faiwer
            /#18860711

            Я пытаюсь объяснить, что именно я подразумеваю под понятием PureComponent. Это лично мое определение.

            Однако в React есть вполне конкретный PureComponent (который совершенно не обязательно чистый, зря они такое название выбрали). Лучше не путать себя и других :)


            Тут не так давно у на уже был вселенский срач на тему React и функциональное программирование. Вроде как решили, что React довольно далёк от настоящей функциональщины.

      • funca
        /#18860377

        разница между Functional Component и React.PureComponent хорошо описана в stackoverflow.com/a/40704083/3297887

        tldr;
        * Functional Component и React.Component с единственным методом render это одно и то же. но первое требует меньше писанины. поэтому eslint рекомендует этот вариант.
        * React.PureComponent это компонент с оптимизированным shouldComponentUpdate (чего нет у первых двух). добавить фичу наследникам React.Component можно реализовав метод лапками. Functional Component — обернуть в HOC с такой функциональностью (можно взять готовый из recompose).

  5. s104
    /#18860217 / +2

    В целом всё верно, но есть пара спорных пунктов.

    Эта ошибка так же очень часто встречается в моей практике. Если вы попытаетесь обновить state в componentDidUpdate или componentDidMount методах, вы получите бесконечный цикл ре-рендера.

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

    Мое персональное мнение это использование immutable-js, как библиотеку, которая добавляет иммутабельные структуры.

    Моё персональное мнение — не использовать эту библиотеку. Она очень тормозная, с большими данными работать невозможно. В своё время обжёгся на этом. Да и с тайпингами геморрой страшный. Использую spread оператор и всем советую.

    • MordorReal
      /#18860253

      Про componentDidMount ответил выше. Если в кратце, то вы вызовите еще одни ре-рендер. И я предлогаю использовать для вещей которые нужно менять в componentDidMount переменные класса.
      ImmutableJs работает быстрее с данными, чем аналогичные структуры из JS (Map, List). Буквально недавно делал бенчмарки по map функции. Если интересно, можете поискать результаты.

  6. dima-f1
    /#18860219

    Если вы попытаетесь обновить state в componentDidUpdate или componentDidMount методах, вы получите бесконечный цикл ре-рендера
    Интересно как можно получить бесконечный цикл ре-рендера при setState внутри componentDidMount если этот метод срабатывает только один сразу после маунтинга компонента и в дальнейшем никак не реагирует на любые изменения стэйта и пропсов?

    По поводу componentDidUpdate то это место где как раз рекомендуется делать апдейт стейта, в случае если например для этого надо сделать какой либо запрос на сервер в зависимости от определенного состояния пропсов, главное правило которое должно здесь соблюдаться это «it must be wrapped in a condition»

    • MordorReal
      /#18860257

      Про componentDidMount ответил выше. Если в кратце, то вы вызовите еще одни ре-рендер. И я предлогаю использовать для вещей которые нужно менять в componentDidMount переменные класса.

    • Sayonji
      /#18860307 / +1

      Речь только про синхронный setState. В том смысле, что если у вас в didMount/didUpdate идет сразу вызов setState, сначит вы могли бы перенести эту логику в конструктор/место вызова первичного setState, т. к. ничего еще не поменялось, и сэкономить рендер. В колбеках ajax-запросов и т.п. само собой можно использовать setState в обоих методах.

  7. jeron-diovis
    /#18860221

    Если вы попытаетесь обновить state в [...] componentDidMount методах, вы получите бесконечный цикл ре-рендера.

    Чего-чего? Я что, пропустил какой-то свежий апдейт Реакта?
    Давайте вместе проверим:
    reactjs.org/docs/react-component.html#componentdidmount

    You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state. Use this pattern with caution because it often causes performance issues. In most cases, you should be able to assign the initial state in the constructor() instead. It can, however, be necessary for cases like modals and tooltips when you need to measure a DOM node before rendering something that depends on its size or position.

    «Use with caution», но «it can be necessary».
    Не вводите людей в заблуждение почём зря.

    • MordorReal
      /#18860261

      Про componentDidMount ответил выше. Если в кратце, то вы вызовите еще одни ре-рендер. И я предлогаю использовать для вещей которые нужно менять в componentDidMount переменные класса.

      • faiwer
        /#18860713 / +2

        И я предлогаю использовать для вещей которые нужно менять в componentDidMount переменные класса

        Тут всё просто. Всё, что не статично и при этом используется в .render должно быть либо в props либо в state. Всё остальное можно хранить где и как угодно. Потому что на изменения .props и .state react умеет реагировать, на ваши обвязки — нет.


        И если ваше значение, которое вы ставите в componentDidMount завязано на render, то стало быть второму render-у быть. Это нормально. Вообще несколько вызовов render в сложных компонентах явление частое. Скажем зачастую приходится вызывать .setState при получении реальной ссылки на DomElement (к примеру, чтобы узнать размеры объекта).

        • MordorReal
          /#18861929

          > Скажем зачастую приходится вызывать .setState при получении реальной ссылки на DomElement (к примеру, чтобы узнать размеры объекта).
          То что вы описали отлично сохраняется как переменная класса. Я как раз и использую ее частенько для сохранения ссылок на элементы в доме. Если можно избежать второго рендера почему этого не сделать? Преждевременная оптимизация? По-моему просто попытка написать нормальный и производительный код. К сожалению, последнее время фронт-енд разработчики перестали заботиться о производительности и размере бандла заранее. Нужно вначале довести до того что бы предложение безбожно тормозило и размер бандла был за 10 Мб, и только после этого заниматься оптимизацией. Мой вопрос заключается в том, что почему если вы знаете проблему, как опытны разработчик, не исправить ее сразу не доводя до крайности? Еще раз. Это не преждевременная оптимизация, это просто попытка написать чистый и производительный код.

          • faiwer
            /#18862107

            Если можно избежать второго рендера почему этого не сделать?

            А как? Вот взять мой недавний случай. Мне надо иметь точные значение offsetWidth и offsetHeight чтобы просчитать нужные коэффициенты для работы с <svg/>. Пока вы DOMElement не получите, вы не узнаете их. А узнав — нужно делать re-render.


            Какой именно use-case вы собираетесь покрыть своим решением? Приведите пожалуйста реальный пример. А то совсем непонятно. Быть может там никакой componentDidMount и не нужен вовсе.

            • MordorReal
              /#18862221

              В вашем примере вам нужно получить ссылку на уже отрендеренный компонент и получить его размеры. Вы сохраняете ссылку на объект в переменной класса, так как эта информация не нужна вам для рендеринга. Далее в componentDidMount вы получаете значения и вызываете ре-рендеринг компонента. В этом случае обходного пути я не вижу, но я пытаюсь донести именно проблему данного подхода. Зачем вам рендерить именно весь компонент? Возможно, можно создать его скрытое зеркало, получить размеры и потом отрендерить сам компонент уже со всем содержимым. В моей практике был всего один раз когда мне понадобилось делать данную бяку: это связано с textarea и текстом в нем. Что бы узнать нужный мне размер textarea для его последующего рендеринга, я отрисовывал его скрытое зеркало в доме, брал размеры, а потом рендерил textarea с текстом. Связано это с тем, что размер текста может быть разным в зависимости от браузера, платформы и так далее. Есть вариант решения данной проблемы используя рендеринг текста в канвасе и считывания его размеров. Вы так же можете написать компонент который будет рендерить текст в канвасе и потом, обращаясь к нему, получать нужные вам размеры. Но описываемый мной пример, скорее, исключения из правила, подтверждающее верность данного правила. Вся реализация этого компонента выглядит как страшный костыль и пугает меня каждый раз когда я вижу его код. Я написал его не потому, что я так захотел, а потому, что порой во фронте случается такая боль.

              • faiwer
                /#18862255

                Зачем вам рендерить именно весь компонент? Возможно, можно создать его скрытое зеркало, получить размеры и потом отрендерить сам компонент уже со всем содержимым.

                Накладные расходы на "зеркало" превысят всю возможную мышиную возню на React. React работает с VDom. А зеркало это реальный DOM. Разница в производительности — на порядки.


                Добавьте к этому то, что реальный размер для зеркала вы получите только, зная реальное место, куда его поместить. Для этого вам может потребоваться реальный DOMElement предка. А его вы до первого render-а так и так не получите. Ух. Приехали едва начав.


                Ну либо захардкодив его ID-ом. Чудесно. В итоге даже несмотря на все ухищрения и порчу кодовой базы — мы проиграли по производительности. Т.к. нам потребовалось создать новый элемент, сделать reflow, удалить его, и потом снова reflow.


                Вся реализация этого компонента выглядит как страшный костыль и пугает меня каждый раз когда я вижу его код.

                Самое время переписать его задействовав двойной рендер :) Поймите, операции с DOM, такие как reflow ОЧЕНЬ дорогие. На фоне их мышиная возня с React-ом (в вашем случае render VDOM для одного v-тега) это просто 0.


                Я вам даже больше скажу. Сейчас в моде повсюду HOC матрёшками делать. Может легко и 4-5 десятков быть на сложных проектах (могу показать скриншот). А каждый (почти) HOC = отдельный компонент = отдельный react-render. И ничего, летает как истребитель. Потому что вся такая обвязка относительно легковесна.

                • MordorReal
                  /#18862271

                  > Накладные расходы на «зеркало» превысят всю возможную мышиную возню на React. React работает с VDom. А зеркало это реальный DOM. Разница в производительности — на порядки.

                  Вы не сможете получить реальные размеры текста не отрендерив его. Про проблему отображения текста я писал выше.
                  > Добавьте к этому то, что реальный размер для зеркала вы получите только, зная реальное место, куда его поместить.
                  В это случае это не нужно. Вы его рендерите считываете размеры и убираете или прячете.
                  > Самое время переписать его задействовав двойной рендер :) Поймите, операции с DOM, такие как reflow ОЧЕНЬ дорогие
                  Это все понятно.
                  > (в вашем случае render VDOM для одного v-тега)
                  Не получите вы реальный размер текста в VDOM.

                  • faiwer
                    /#18862315

                    Не получите вы реальный размер текста в VDOM.

                    Вы получите его в componentDidMount без всяких зеркал, прятаний и прочих шаманств. Отрендерили сколько надо, получили размер, дорендерили с нюансами. И даже моргать ничего не будет.

                    • MordorReal
                      /#18862351

                      Смотрите. Изначальный посыл был не менять state в componentDidMount, так как его изменения заставят перерендериться компонент и если, например, компонент рендерит в дом 1000 компонентов, то вы сильно замедлите итоговый рендеринг.
                      Тот вариант который описывал я и который предлагаете вы, вообще из другой оперы. Он не требует сохранения чего бы то ни было в state. Он маленький. Ссылку на компонент вы сохраняете в переменной класса. В componentDidMount вы получаете размер компонента по ссылке. Далее с этой информаций вы можете делать что хотите. Вы можете запросить эти данные из другого компонента, вы можете поменять в итоге state и перерендерить этот маленький компонент. Что угодно. Это будет в итоге не существенно. Но вот если вы таким образом будете рендерить 1000 компонентов и все их потом перерендеривать, это будет уже существенно и разницу вы заметите.

                      • faiwer
                        /#18862419 / +1

                        Изначальный посыл был не менять state в componentDidMount, … если, например, компонент рендерит в дом 1000 компонентов, то вы сильно замедлите итоговый рендеринг

                        Давайте начнём с того, что если 1 компонент рендерит целую 1000, то у вас что-то не так с архитектурой приложения. Что это за компонент такой? Список в 1000 элементов? Если да, то такой список является болезненной зоной для любого фреймворка, пока вы не задействуете тут VirtualDom. Если же дочерних компонент мало, а 1000 набирается при рендере вглубь — то откройте для себя PureComputed-ы. Тут проблема именно в том, что НЕ должно быть такого компонента, update которого впустую затрагивает 1000-у других компонент. Это проблемы с архитектурой вашего React приложения. И вместо того, чтобы иметь такого монстра, а все update делать "руками" (setAttribute, appendChild, remove и пр.) надо переписать эту часть приложения.


                        Далее с этой информаций вы можете делать что хотите.

                        Собственно почти всегда вы делаете именно setState. Потому что именно он вам и нужен. Вы получили какую-то необходимую информацию из DOM, и теперь рендерите компонент снова учитывая её. Такой рендер, при правильной архитектуре — копеечный. Вглубь он не уходит, реконсиляция быстрая, и пара операций над DOM-ом (какие-нибудь аттрибуты или стили выставить). Не нужны тут никакие зеркала.


                        Всё остальное в вашем комментарии у вас, похоже, базируется на том, что у вас есть некий монстр-пожиратель-ресурсов, который рендерит 1000 компонент и трогать его через React себе дороже.

                        • MordorReal
                          /#18862495

                          Не нужны тут никакие зеркала.

                          Усложним пример. В прочем, мы уже залезли в дебри и крайние случаи. Вам нужно снять размеры с дива, а отрендерить, например, таблицу. Или снять размеры с дива с целью выяснения максимальной высоты строки. Или любых два разных элемента на ваш выбор.


                          Всё остальное в вашем комментарии у вас, похоже, базируется на том, что у вас есть некий монстр-пожиратель-ресурсов, который рендерит 1000 компонент и трогать его через React себе дороже.

                          Очень часто люди в componentDidMount меняют данные которые влияют на рендеринг, и делают это, как правило, не в каких то особых случаях, а просто потому, что захотелось. Мой посыл избегать этого и:


                          You may call setState() immediately in componentDidMount(). It will trigger an extra rendering, but it will happen before the browser updates the screen. This guarantees that even though the render() will be called twice in this case, the user won’t see the intermediate state. Use this pattern with caution because it often causes performance issues.

                          То что я и хотел донести:
                          Use this pattern with caution — используйте с осторожностью, потому что — it often causes performance — это часто влияет на производительность.
                          https://reactjs.org/docs/react-component.html#componentdidmount

                          • faiwer
                            /#18862557

                            Use this pattern with caution

                            Это ? разумно. Откройте md страницу этого правила (no-did-mount-set-state) там речь про property/layout thrashing, которые и правда нежелательны.


                            О чём по факту эти правила? Они о том, что нет смысла использовать setState в componentDidMount в тех случаях, когда это можно сделать в конструкторе, этого можно не делать вовсе, это можно сделать через мемоизатор и пр… Т.е. тогда когда человек просто творит какую-то дичь, не поняв, зачем componentDidMount вообще нужен.


                            Вам нужно снять размеры с дива, а отрендерить, например, таблицу.

                            Взять размеры одного компонента, чтобы повлиять на размеры какого-либо другого компонента. Так? Странная ситуация, но допустим. В таком случае мы не используем эти размеры в .render методе Div-а. Мы их используем в .render-методе Table-а. И setState будем задавать уже у него. Каким образом будем их связывать — вопрос отдельного топика. Можно через props вышестоящего компонента.


                            Что из этого всего ^ следует? А вот это и следует. Я с самого начала об этом написал. И да, об этом пишет и документация React-а. И это никак не связано с вашим текстом react/no-did-mount-set-state. Прочитайте тот мой комментарий внимательно, пожалуйста. Это очень простая штука. В React-е нет неопределённости между выбором писать в поле класса, или в state. Это всегда однозначно ясно. И componentDidMount тут не причём.

                            • MordorReal
                              /#18862587 / +1

                              Это всегда однозначно ясно.

                              Как показывает практика, к сожалению, не всем. И я очень рад, что мы с вами обсудили данный топик.
                              Все что влияет на рендер одного компонента в state. Двух и более в props. Остальное в переменные класса. — Для меня это золотое правило которым я руководствуюсь при выборе где мне хранить данные.
                              Я постараюсь собрать все, о чем мы с вами здесь говорили и уточнить react/no-did-mount-set-state, раз он вызвал столько неоднозначности у читающих.

  8. zwerg44
    /#18860289 / +1

    Думаю, следовать большинству правил, поможет грамотно сконфигурированный в IDE линтер.
    Кажется все перечисленные правила есть тут, и детектируемы линтерами.
    Я, в своих проектах, использую eslint-config-airbnb. Это набор правил для стайлгайда airbnb и airbnb-react.

    • MordorReal
      /#18860301

      Airbnb, в целом, сделала большую работу по агригации полезных eslint правил, но я предпочитаю, более строгие варианты в некоторых местах. Исключительно для улучшения чтения кода и дальнейшего его поддерживания.

      Кажется все перечисленные правила есть тут, и детектируемы линтерами.

      Все верно, но я ставил своей целью не только дать ссылки на эти правила, но и объяснить почему их необходимо выполнять. Надеюсь у меня получилось.

  9. iproger
    /#18860291

    Почему последние годы в js все стали использовать const? Почему не let или var?

    • MordorReal
      /#18860321 / +2

      const — это константа. То есть переменная, которой значение можно присвоить только один раз.
      let — это переменная, которой значение можно присвоить несколько раз.
      В сети есть очень много бенчмарков которые показывают сравнение производетельности let, const и var.
      Так же рекомендую отличную статью от Эрика
      Мои личные рекомендации — использовать только const кроме тех редких случаев когда вам действительно нужна мутация данных (поможет в этом let).

      • faiwer
        /#18860717

        const — это константа. То есть переменная, которой значение можно присвоить только один раз.

        Не надо так сильно упрощать объяснения. Народ потом от этого лютует. Никакие это не константы :)


        iproger, const в js это странная штука. Написав const a = что_нибудь, мы присваиваем некоему a значение что_нибудь, и в последствии присвоить a что-либо ещё мы не сможем. Это напоминает константы из других языков. Но по факту, внутри этого что_нибудь мы можем что-нибудь поменять. Т.е. по факту константной является только привязка нашего a к данному что_нибудь. Никаких других гарантий слово const не даёт.

        • funca
          /#18861119

          faiwer, не надо все усложнять) там MordorReal все правильно написал: const объявляет константную переменную.

          а не константное что_нибудьзначение. для значений переменной правила игры остаются без изменений: они либо примитивы (immutable), либо объекты (mutable, с оговорками). все очень просто и это прекрасно.

          • faiwer
            /#18861135

            А вам не кажется, что "константная переменная" это оксюморон? :)

            • extempl
              /#18862953 / +1

              Тут "переменная" не как переменная, а как ссылка на значение.

        • VolCh
          /#18864483

          const в js — это постоянное значение. Значение может быть примитивного(скалярного) или ссылочного (объектного) типа.

          • faiwer
            /#18864495

            Да я в курсе, мне просто кажется, что такие вещи надо "на пальцах" и в примерах показывать, const в JS несколько контр-интуитивны. Особенно если человек пришёл из языка, где const это настоящие константы.

            • gearbox
              /#18866777 / -1

              а что ненастоящего в const js? Ссылочный тип? Так скалярное значение ссылки — адрес, и вы его изменить не сможете. Все там интуитивно, просто надо немного low level-а в голове держать.

            • staticlab
              /#18869089 / +1

              Да просто это final на самом деле. Просто, видимо, ранее именно такое ключевое слово не было зарезервировано разработчиками стандарта, вот они и взяли const.

            • VolCh
              /#18869135

              Ну не знаю. Для меня контринтуитивно когда конструкция типа const user = new User() подразумевает, что мутирующие методы и присваивание свойств объекта класса User мы вызывать не можем. И да, это с других языков типа ассемблера и C, где мы можем константой (#define и подобные) объявить адрес в памяти, изменить его не можем, но память от этого read only не становится — для этого надо дополнительные усилия прилагать, если это вообще возможно в рамках аппаратной архитектуры.

    • k12th
      /#18860549

      Не var потому что у var причудливая область видимости, hoisting и вот это вот всё, поведение let и const гораздо более предсказуемо и интуитивно понятно.
      const, а не let — как правило (хоть и не всегда) переназначение переменной это не очень удачная идея. Можно затереть оригинальное значение и получить какой-нибудь смешной баг. В простом и ясном коде 99.9% переменных не меняют свои значения. Ну и плюс мода на ФП влияет.

    • PerlPower
      /#18860641 / -1

      Во всех других языках тоже есть константы, но там их при сравнимых случаях не лепят. В пользу const можно при желании привести аргументы и тут же к ним контрагрументы. Например const function getUser {} формально не может быть заменена другой функцией по ошибке. Но! С одной стороны сейчас и так каждый файл завернут в модуль или неймспейс, а с другой непонятно во что транслируется тот же const бабелом при допустим трансляции в ES5, которая еще бывает нужна.

      Разница по скорости let vs const в ряд ли станет тем местом в которое вы упретесь, во всяком случае если это не какой-нибудь новый UI фреймворк, где вы будете мониторить переменные на наличие изменений.

      Лично мне использование const с точки зрения удобства видится крайне сомнительным, потому что заранее знать сколько у меня переменных и какие я буду менять, а какие будут переданы в функцию с сайд-эффектом, я не могу. Поэтому мне проще везде ставить let, а const делать только где он правда очевиден, типа пары констант в начале класса.

      На собеседованиях конечно нужно говорить, что только угроза физической расправы или же крайняя нужда может вынудить вас использовать let вместо const. Потому что если человек искренне считает, что если на торт из разноцветного дерьма, коим на сегодняшний день является весь фронтенд, положить маааааленький кусочек синтаксического сахара, то это сделает код его значительно лучше, то переубеждать его бесполезно.

      • vvadzim
        /#18860731

        заранее знать сколько у меня переменных и какие я буду менять, а какие будут переданы в функцию с сайд-эффектом, я не могу.
        Ну вот по-этому const и использую. let использую как маркер, что где-то гарантированно есть переприсвоение. Увидел let — ищи сайд-эффекты. Соответственно переприсвоение появляется — меняю const на let. Переприсвоение исчезает — меняю let на const. В комитах видно идеально, на ревью тут же повышенное внимание.

        • PerlPower
          /#18860919 / -1

          const на объектах не работает и их нужно сперва замораживать, а для этого Object.freeze создает новую копию объекта. По мне это так это пустая трата времени, как и повторный проход по алгоритму с целью расставить const вместо let. А потом обратно вставить let когда в голову придет мысль что-то добавить.

          Формально вы правы — ваш код будет лучше и правильней, но я уверен, что в любом реальном проекте найдется еще десяток проблем которые важнее чем это цирк с const. И в целом вот эта новая практика пихания const в каждую дырку создает впечатление изощренной прокрастинации и взращивания ЧСВ на пустом месте — смотрите какой у нас хороший и правильный код, все функциональненько, модно и молодежно. В других языках константы были и до 2015 года, и почему-то идиома их гипертрофированного применения почти нигде не прижилась. Как и функциональщина, которую каждое поколение программистов с восторгом откапывает, вдовль наигрывается и закапывает обратно.

          • faiwer
            /#18860931

            Относитесь к этому как к CodeStyle-у. Ведь у каждой команды свои заморочки. Так и тут. Кто-то предпочитает такие вот дополнительные ограничения, которые, скажем, меня лично, заставляют следить за кодом больше, чем без них. Кому-то и без них прекрасно живётся. Это и правда малозначительная ерунда. Уж если кто-то умудряется писать код в редакторах без подсветки синтаксиса, то, что уж говорить про const-let-var...

          • vvadzim
            /#18861123

            Я вроде как конкретный пример привел, зачем именно мне const. С модой у меня самого непростые отношения.
            Не работает на объектах — ну да. Плохо. Лучше бы работало. Пока так. Идеала нет, для меня это не повод отказываться от моего кейса.
            Ну и не совсем понял зачем нужен повторный проход) Мне редко нужно менять const на let и наоборот, но может это у меня алгоритмы несложные.

  10. vvadzim
    /#18860727

    <не сюда>

  11. kashey
    /#18860739 / +1

    > react/forbid-component-props
    Передача классов между в компоненты, в случае следовании БЕМ-нотации, обязательна и полезно.
    Класс компонента определяет его как Блок, класс передаваемый ему из-вне — как Элемент.
    Их свойства не пересекаются, и никогда не конфликтуют.

    > react/no-array-index-key
    Очень часто у «данных» нет ID. Тут поможет библиотека типа react-uid, которая превратить в key сами данные.

    • faiwer
      /#18860757

      react-uid

      Я пока открывал ссылку подумал, что там внутри сериализация. Но нет, там её нет. Интересное решение. Использует внутри себя WeakMap (он не полифилится). Т.е. годится только для immutable-значений. И гарантировано убъёт существующий экземпляр компоненты, если ссылка на объект поменялась. Штука спорная, но решение интересное ;)

      • kashey
        /#18861075

        WeakMap полифилиться без проблем и есть в core-js, хотя уже в IE11 он нативный.
        Насчет immutable — все верно. Если ключа нет, и ключем является обьект — его изменение изменит ключ. На безрыбье и рак — рыба.

        • faiwer
          /#18861087

          WeakMap полифилиться без проблем и есть в core-js

          Оно? ( https://github.com/polygonplanet/weakmap-polyfill ). По сути тут мутация за счёт применения defineProperty к самому объекту. Я бы не назвал это "без проблем", но на худой случай сгодится. Или есть какие-то более хитрые решения?

          • kashey
            /#18861113

            Все известные мне полифилы построены на этом принципе — использование переданного обьекта для хранение значения.
            Есть вырожденный случай для IE(IE8) конкретно для DOM обьектов, где для них _есть_ WeakMap (а для не-DOM его нет).

  12. faiwer
    /#18860745 / +1

    Статья очень не понравилась. Потому что переполнена спорными пунктами.


    react/forbid-component-props

    Не понял что именно имеется ввиду. Но судя по участку кода, предлагается css-классы назначать внутри дочернего компонента, исходя из переданных ему bool-значений. Так вот. Это разумно. Но только когда наш внутренний компонент "в курсе" всей этой ситуации. А когда он простая пешка, какой-нибудь UI примитив, туда просто глупо передавать всю эту бизнес-логику, и все классы для него нужно подготовить оттуда, где этой логике место быть.


    В общем, как обычно, it depends. Никакая это не "распространённая ошибка". Надо просто понимать суть вещей, и тогда ясно, когда и что применить.


    react/forbid-prop-types

    Опять же it depends. В сложных проектах частое явление, когда какие-нибудь props прокидываются на уровень ниже, где могут быть расфасованы вообще по разным компонентам, с разными API. И тут нам на помощь приходят эти самые .any, .object и пр… А наиболее точные декларации описаны на том уровне, где они по факту задействуются.


    Тоже никакая это не ошибка, если человек понимает, что делает.


    react/no-access-state-in-setstate

    Пожалуйста, уберите оттуда prevState.counter++ и пр., а то глаза кровоточат. Зачем вы меняете поле counter в prevState ради задания нового counter в newState? Строчку сэкономили? Вы понимаете принцип работы ++ i и i ++? Это мутирующие операции. Да, здесь, возможно, это не приведёт к проблемам, но оно вот вам надо?


    react/no-array-index-key

    На моей практике в 90+% случаев использование индексов в качестве key более, чем оправдано. Перестаньте уже писать это из статьи в статью. Ей богу, ну сколько можно. Доходит до того, что новички, начитавшись этих "ужасов" начинают генерировать ID-ки там где этого делать не надо, сильно усложняя систему. А по факту, если у вас коллекция не предусматривает каких-либо смещений, удалений, добавлений и пр. внутри себя, то боятся совершенно нечего. Мне кажется, таких коллекций большинство. И да, почти всегда в них нет никаких id и др. полей, к которым можно прицепиться.


    Очень важно понимать назначение key, и уметь им правильно пользоваться (это крутой инструмент, на самом деле). Но сеять панику и писать "избегайте этой ошибки в будущем" точно излишне. Надо понимать работу механизма, а не просто чего-то зачем-то избегать, и усложнять себе этим жизнь.


    react/no-did-mount-set-state

    Рекомендую перечитать документацию и прочитать когда надо использовать поля класса, а когда нет. Мне кажется, вы не совсем понимаете этого. И да, в ряде случаев дополнительный вызов render более, чем обоснован. Выше в комментариях я расписал более детально.


    react/no-direct-mutation-state

    Мутация state и правда зло. Поэтому уберите плз тот кусок кода с ++, пожалуйста :) А immutable-js очень уж на любителя.


    react/prefer-stateless-function

    Не использую в своём коде (почти) функциональной нотации компоненты. Вижу это очень неудобным в большинстве случаев, т.к. с течением времени компоненты усложняются и приходится сильно корёжить старый код. Поэтому сразу пишу в классах (к тому же использую только PureComputed). Зачем вы такой подход описываете, как ошибку мне не понять. К тому же сами же пишете, что для всех компонент, где есть props вы задаёте propTypes. Т.е. вы ниже всё равно задаёте MyComponent.propTypes =. Стало быть ради чего вы писали стрелочную функцию вместо класса? Потом её ещё и pure() обернёте, да? :)


    Нет, можно и так и так. Никаких проблем тут нет. На вкус и цвет, как говорится, все фломастеры разные. Но ошибки тут точно никакой нет. Уберите этот пункт совсем, зачем он?


    react/jsx-no-bind

    Самая большая ошибка из всех. И ей уделён всего 1 абзац. Странно вы приоритеты расположили. Благо об bind проблеме не писал уже разве что только ленивый и материалов навалом.


    На мой взгляд эта статья полна предрассудков и субъективщины. А подаётся это всё как типовые ошибки.

    • MordorReal
      /#18861981

      Спасибо за вашу критику.
      > На мой взгляд эта статья полна предрассудков и субъективщины.
      собственно как и ваш комментарий :)
      Я описал свой личный опыт и те проблемы с которыми столкнулся лично я. Вы, возможно, еще их не видели, а может просто не обратили на них внимание.
      > Пожалуйста, уберите оттуда prevState.counter++
      Спасибо. Поправил. Действительно проглядел данный кусок кода.

      • faiwer
        /#18862183 / +1

        собственно как и ваш комментарий

        Дык, а давайте по делу. Пункт за пунктом. Просто это вы, а не я, написали статью в менторском тоне, для "молодых разработчиков", куда записали большое кол-во противоречивых моментов. Вы бы туда ещё табы-vs-пробелы затолкали, ей богу :)


        В то время как реальных типовых ошибок я в статье почти не вижу. Скажем где совершенно регулярная ошибка: городить большие шибко-умные компоненты, вместо множества мелких? Где что-нибудь про props-hell? Где про хардкод? Где про разделение обязанностей (скажем fetch-и где попало)? Где про не-чистые render-методы? Новички правда любят в render что-нибудь мутировать. Ну и т.д… Зато первым же пунктом пишете про не передавайте в нижестоящий компонент className :) WAT?

        • MordorReal
          /#18862245

          Я и не претендую быть истиной в одной инстанции. Я поделился своим мнением и опытом. Ваше право не согласиться и оспорить его. В споре, как говорится, рождается истина.
          Вы написали, что моя статья субъективна. Она субъективна и я это не отрицаю. Вы описали ваши замечания в комментарии. Это ваши замечания которые ВЫ считаете правильными. То-есть они субъективны.
          К сожаление, у меня нет много свободного времени для описания всех имеющихся проблем в Реакте. Я описал только самые популярные на мой взгляд. Для этих проблем не глупые люди написали даже отдельные правила для eslint (надеюсь, вы не будете отрицать, что если их написали и добавили в eslint, значит они имеют место быть и даже считаются совсем не субъективными проблемами). Если у вас много опыта и есть время, welcome. Я с удовольствием почитаю про ваш опыт и проблемы в статье на хабре.

          • faiwer
            /#18862283

            Для этих проблем не глупые люди написали даже отдельные правила для eslint

            А у них справка есть? :) В eslint-е огромное кол-во правил. Они даже конфликтуют с друг другом, если вы их одновременно все включите. К тому же не забывайте, что при надобности их отключают условным комментарием.


            значит они имеют место быть и даже считаются совсем не субъективными проблемами

            ROFL, шутка дня. Вы похоже ооочень далеки от мира eslint :)

            • MordorReal
              /#18862289

              Что то вы отшутились на мои комментарии :) Ну тогда и я могу посмеяться :)

  13. Metaller
    /#18860765 / +1

    > Первая распространенная ошибка это передача 'style' и 'className' как props в ваш компонент.
    И ссылка на статью 2015 года. На дворе 2018. Для корректной работы CSSinJS (styled components, JSS etc.) необходимо прокидывать className в свой компонент.

    Более правильным вариантом будет при помощи того же classnames склеивать приходящий в виде пропсов className с собственными классами компонента.

    > Не Используйте Bind И Arrow Function В Jsx. Никогда. Больше.
    Это слишком категоричное заявление. Сколько копий уже сломано на эту тему. Я просто оставлю эту ссылку здесь.

    TL;DR в моем вольном переводе
    Преждевременная оптимизация — корень всех зол. Если вы не делаете измерения производительности, вы даже не знаете, работает ли ваша оптимизация, и вы точно не знаете, не сделала ли она только хуже.

    • faiwer
      /#18860771

      Я просто оставлю эту ссылку здесь.

      Пожалуйста, не надо её нигде оставлять. Лучше её закопать.

      • Metaller
        /#18860781

        Это мнение, как и все другие, имеет право на существование, давайте не будем опять начинать тот срач. Ну вы в курсе, какой.

        • faiwer
          /#18860801

          Нет, я не в курсе что за срач. И что это за мнение такое, что его критиковать нельзя. Статья Ryan Florence подана как поучительная, да ещё и c "Premature optimization is the root of all evil". А на деле несёт большое зло. Ведь кто-то ею даже руководствуется. И потом на каждый чих, на каждый action мы получаем rerender огромных кусков VDOM и всё тормозит. Избегая написания () => { впользу (){ автор плодит неконтролируемые тормоза. И учит этому других. Полагаю, он просто ещё не столкнулся ни с одним маломальски большим приложением. Не смотрел его flame graph, схватившись за голову обеими руками.

          • kashey
            /#18861065

            На самом деле все не так страшно — если уж что-то начало апдейт дерева, и добавляет всюду новые onClick — ну возможно можно использовать более «интересные» решения чтобы распространение этого апдейта прекратить.

  14. serf
    /#18863679

    Весь код написан в ES6 стиле, поэтому, что бы повторить его вам нужно использовать Babel в вашем проекте (а еще есть такие кто его не использует?).


    Конечно есть такие кто не использует Babel, есть ведь TypeScript.