Привет, Хабр.

Ровно год прошел с момента, как я начал изучать React. За это время я успел выпустить несколько небольших мобильных приложений, написанных на React Native, и поучаствовать в разработке web-приложения с использованием ReactJS. Подводя итог и оглядываясь назад на все те грабли, на которые я успел наступить, у меня родилась идея выразить свой опыт в виде статьи. Оговорюсь, что до начала изучения реакта, у меня имелось 3 года опыта разработки на c++, python, а также мнение, что во фронтенд разработке нет ничего сложного и разобраться во всем мне не составит труда. Поэтому в первые месяцы я пренебрегал чтением обучающей литературы и в основном просто гуглил готовые примеры кода. Соответственно, примерный разработчик, который первым делом изучает документацию, скорее всего, не найдет для себя здесь ничего нового, но я все-таки считаю, что довольно много людей при изучении новой технологии предпочитают путь от практики к теории. Так что если данная статья убережет кого-то от граблей, то я старался не зря.

Совет 1. Работа с формами


Классическая ситуация: имеется форма с несколькими полями, в которые пользователь вводит данные, после чего нажимает на кнопку, и введенные данные отправляются на внешний апи/сохраняются в state/выводятся на экран — подчеркните нужное.

Вариант 1. Как делать не надо


В React существует возможность создания ссылки на узел DOM или компонент React.

this.myRef = React.createRef();

C помощью атрибута ref созданную ссылку можно присоединить к нужному компоненту/узлу.

<input id="data" type="text" ref={this.myRef} /> 

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

class BadForm extends React.Component {
  constructor(props) {
    super(props);
    this.myRef = React.createRef();
    this.onClickHandler = this.onClickHandler.bind(this);
  }

  onClickHandler() {
    const data = this.myRef.current.value;
    alert(data);
  }

  render() {
    return (
      <>
        <form>
          <label htmlFor="data">Bad form:</label>
          <input id="data" type="text" ref={this.myRef} />
          <input type="button" value="OK" onClick={this.onClickHandler} />
        </form>
      </>
    );
  }
}

Как внутренняя обезьянка может попытаться оправдать данное решение:

  1. Главное, что работает, у тебя еще 100500 задач, а сериалы не смотрены тикеты не закрыты. Оставь так, потом поменяешь
  2. Смотри, как мало кода нужно для обработки формы. Объявил ref и получай доступ к данным откуда хочешь.
  3. Если будешь хранить значение в state, то при каждом изменении вводимых данных все приложение будет рендериться заново, а тебе ведь нужны только итоговые данные. Так этот метод получается еще и по оптимизации хорош, точно оставь так.

Почему обезьянка не права:

Пример выше — классический антипаттерн в React, который нарушает концепцию однонаправленного потока данных. В данном случае ваше приложение никак не сможет отреагировать на изменения данных при вводе, так как они не хранятся в state.

Вариант 2. Классическое решение


Для каждого поля формы создается переменная в state, в которой будет храниться результат ввода. Атрибуту value присваивается данная переменная. Атрибуту onСhange присваивается функция, в которой через setState() изменяется значение переменной в state. Таким образом, все данные берутся из state, а при изменении данных изменяется state и приложение рендерится заново.

class GoodForm extends React.Component {
  constructor(props) {
    super(props);
    this.state = { data: '' };
    this.onChangeData = this.onChangeData.bind(this);
    this.onClickHandler = this.onClickHandler.bind(this);
  }

  onChangeData(event) {
    this.setState({ data: event.target.value });
  }

  onClickHandler(event) {
    const { data } = this.state;
    alert(data);
  }

  render() {
    const { data } = this.state;
    return (
      <>
        <form>
          <label htmlFor="data">Good form:</label>
          <input id="data" type="text" value={data} onChange={this.onChangeData} />
          <input type="button" value="OK" onClick={this.onClickHandler} />
        </form>
      </>
    );
  }
}

Вариант 3. Продвинутый. Когда форм становится много


У второго варианта существует ряд недостатков: большое количество стандартного кода, для каждого поля необходимо объявить метод onСhange и добавить переменную в state. Когда дело доходит до валидации введенных данных и вывода сообщений об ошибке, то количество кода возрастает еще больше. Для облегчения работы с формами существует прекрасная библиотека Formik, которая берет на себя вопросы, связанные с обслуживанием форм, а также позволяет с легкостью добавить схему валидации.

import React from 'react';
import { Formik } from 'formik';
import * as Yup from 'yup';

const SigninSchema = Yup.object().shape({
  data: Yup.string()
    .min(2, 'Too Short!')
    .max(50, 'Too Long!')
    .required('Data required'),
});

export default () => (
  <div>
    <Formik
      initialValues={{ data: '' }}
      validationSchema={SigninSchema}
      onSubmit={(values) => {
          alert(values.data);
      }}
      render={(props) => (
        <form onSubmit={props.handleSubmit}>
          <label>Formik form:</label>
          <input
            type="text"
            onChange={props.handleChange}
            onBlur={props.handleBlur}
            value={props.values.data}
            name="data"
          />
          {props.errors.data && props.touched.data ? (
            <div>{props.errors.data}</div>
          ) : null}
          <button type="submit">Ok</button>
        </form>
      )}
    />
  </div>
);

Совет 2. Избегайте мутаций


Рассмотрим простое приложение типа to-do list. В конструкторе определим в state переменную, в которой будет храниться список дел. В методе render() выведем форму, через которую будем добавлять дела в список. Теперь рассмотрим, каким образом мы можем изменить state.

Неправильный вариант, приводящий к мутации массива:

this.state.data.push(item);

В данном случае массив действительно изменился, но React об этом ничего не знает, а значит метод render() не будет вызван, и наши изменения не отобразятся. Дело в том, что в JavaScript при создании нового массива или объекта в переменной сохраняется ссылка, а не сам объект. Таким образом, добавив в массив data новый элемент, мы изменяем сам массив, но не ссылку на него, а значит значение data, сохраненное в state, не изменится.

С мутациями в JavaScript можно столкнуться на каждом шагу. Чтобы избежать мутаций данных, для массивов используйте spread оператор либо методы filter() и map(), а для объектов — spread оператор либо метод assign().

const newData = [...data, item];
const copy = Object.assign({}, obj);

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

Не делайте так!

this.state.data = [...data, item];

Также избегайте мутации state. Даже если вы используете setState(), мутации могут привести к багам при попытках оптимизации. Например, если вы передадите мутировавший объект через props в дочерний PureComponent, то данный компонент не сможет понять, что полученные props изменились, и не выполнит повторный рендеринг.

Не делайте так!

this.state.data.push(item);
this.setState({ data: this.state.data });

Корректный вариант:

const { data } = this.state;
const newData = [...data, item];
this.setState({ data: newData });

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

Корректный вариант, если следующее состояние зависит от текущего:

this.setState((state) => {
  return {data: [...state.data, item]};
});

Совет 3. Эмуляция многостраничного приложения


Ваше приложение развивается, и в какой-то момент вы понимаете, что вам нужна многостраничность. Но как же быть, ведь React является single page application? В этот момент вам может прийти в голову следующая безумная идея. Вы решаете, что будете хранить идентификатор текущей страницы в глобальном состоянии своего приложения, например, используя redux store. Для вывода нужной страницы вы будете использовать условный рендеринг, а переходить между страницами, вызывая action с нужным payload, тем самым изменяя значения в store redux.

App.js

import React from 'react';
import { connect } from 'react-redux';
import './App.css';
import Page1 from './Page1';
import Page2 from './Page2';

const mapStateToProps = (state) => ({ page: state.page });

function AppCon(props) {
  if (props.page === 'Page1') {
    return (
      <div className="App">
        <Page1 />
      </div>
    );
  }
  return (
    <div className="App">
      <Page2 />
    </div>
  );
}
const App = connect(mapStateToProps)(AppCon);
export default App;

Page1.js

import React from 'react';
import { connect } from 'react-redux';
import { setPage } from './redux/actions';

function mapDispatchToProps(dispatch) {
  return {
    setPageHandle: (page) => dispatch(setPage(page)),
  };
}

function Page1Con(props) {
    return (
      <>
        <h3> Page 1 </h3>
        <input 
            type="button"
            value="Go to page2"
            onClick={() => props.setPageHandle('Page2')}
        />
      </>
    );
}

const Page1 = connect(null, mapDispatchToProps)(Page1Con);
export default Page1;

Чем это плохо?

  1. Данное решение — пример примитивного велосипеда. Если вы знаете, как сделать такой велосипед грамотно и понимаете на что идете, то не мне вам советовать. В противном случае, ваш код получится неявным, запутанным и излишне сложным.
  2. Вы не сможете пользоваться кнопкой назад в браузере, так как история посещений не будет сохраняться.

Как это решить?

Просто используйте react-router. Это отличный пакет, который с легкостью превратит ваше приложение в многостраничное.

Совет 4. Где расположить запросы к api


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

  • constructor()
  • static getDerivedStateFromProps()
  • render()
  • componentDidMount()

Разберем все варианты по порядку.

В методе constructor() документация не рекомендует делать что-либо, кроме:

  • Инициализации внутреннего состояния через присвоение объекта this.state.
  • Привязки обработчиков событий к экземпляру.

Обращения к api в этот список не попадают, так что идем дальше.

Метод getDerivedStateFromProps() согласно документации существует для редких ситуаций, когда состояние зависит от изменений в props. Снова не наш случай.

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

Так что идеальным местом для обращений к внешнему api является метод componentDidMount().

Заключение


Примеры кода можно найти на github.

Комментарии (38)


  1. Koneru
    25.09.2019 11:32

    А сколько лет статье? Уже даже PureComponent уходит в прошлое, уступая мемоизированным функциональным компонентам на хуках.


    1. hd_keeper
      25.09.2019 13:47

      А в чём от них польза? Всё то же самое, и даже больше, можно делать и на классах-компонентах.


      1. Koneru
        25.09.2019 23:49
        +1

        Во всем хуки лучше. Но хуков раньше не было и есть legacy, который нужно поддерживать. Нужно учить что-то новое.
        Из плюсов, например, удобно работать с контекстом и рефами. Проще разбивать на логические блоки. Проще типизировать(TS). Меньше кода, как на этапе разработки, так и после Babel/Terser. Выше скорость исполнения кода(спорный момент), многое зависит от прямоты рук, но если идеальный случай, то функциональные компоненты работают быстрее. И еще куча всего описано в доке.


        1. Druu
          26.09.2019 09:09

          Из плюсов, например, удобно работать с контекстом и рефами. Проще разбивать на логические блоки. Проще типизировать(TS). Меньше кода, как на этапе разработки, так и после Babel/Terser.

          Но это все связано не с хуками, а с тем, что для хуков соответствующий сахарок запилили, а для классов — не запилили. Хотя запилить можно, и тогда с классами можно будет делать все точно так же, как с хуками, только проще и удобнее.


          1. Koneru
            26.09.2019 10:41

            Но:
            1) Не запилили улучшения для работы с классовыми компонентами
            2) Со скоростью работы тоже вряд-ли что-то смогут сделать
            3) С типизацией что можно сделать?
            4) И что же такого грандиозного и архитектурно правильного можно сделать на классах, чего нельзя сделать на функциональных компонентах?


            1. mayorovp
              26.09.2019 10:43

              А что не так со скоростью работы классов?


            1. Druu
              26.09.2019 11:30

              1) Не запилили улучшения для работы с классовыми компонентами

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


              2) Со скоростью работы тоже вряд-ли что-то смогут сделать

              А можно увидеть какой-то кейз, в котором классовые компоненты тормозят, а прямая замена на функциональные — тормоза убирает?


              3) С типизацией что можно сделать?

              С типизацией как раз у функциональных компонент проблемы. В классовых то что не так?


              4) И что же такого грандиозного и архитектурно правильного можно сделать на классах, чего нельзя сделать на функциональных компонентах?

              Во-первых, как минимум, для классовых компонент можно было бы воспользоваться имеющейся языковой инфраструктурой, а не изобретать велосипеды из костылей, чтобы оно просто заработало. Это, в общем-то, уже вполне достаточное преимущество.


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


              Ну и в-третьих, у вас самого не возникает чувство некоего противоречия, когда вы в функциональный компонент добавляете состояние? Эта концепция же сама по себе, просто by design, отдает одеванием штанов через голову. Если вам в компоненте нужно состояние — то значит компонент просто не должен быть функциональным. Если же вы пытаетесь смешать ужа с ежом то и получаете эту недоделанную солянку из костылей в виде "хуков".


              1. Koneru
                26.09.2019 14:46

                Спасибо за конструктивную критику.

                С типизацией как раз у функциональных компонент проблемы. В классовых то что не так?

                Я не сталкивался именно с проблемами типизации ни FC(функциональных компонентов), ни PC(компонентах на классах), но хуки в FC умеют в выведение типов
                const [count, setCount] = useState(0);

                а также тип и значение находяться в непосредственной близости без загромождения кода:
                const [someObject, setSomeObject] = useState<ISomeInterface>(props.someObject);


                могли и запилить

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

                А можно увидеть какой-то кейз, в котором классовые компоненты тормозят, а прямая замена на функциональные — тормоза убирает?

                Да, тут доказать будет сложно, постараюсь на выходных сделать замеры и доказать истинность(ложность) моего утверждения. Без тестов разговор не сложиться.

                имеющейся языковой инфраструктурой

                А что из жизненного цикла PC или FC не относиться(относиться) к языковой инфраструктуре? Не совсем понятен аргумент вообще.

                в случае классовых компонент аналог хуков был бы значительно более гибок

                А можно пример желаемого синтаксиса, что-то не приходит мыслей о том как это может выглядеть.

                функциональный компонент добавляете состояние


                Не вижу противоречий, загуглил с целью найти тайный смысл, но все забито ссылками на FC в React.

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

                Почему?


                1. Druu
                  26.09.2019 18:22

                  Я не сталкивался именно с проблемами типизации ни FC(функциональных компонентов), ни PC(компонентах на классах), но хуки в FC умеют в выведение типов

                  Этот тип же в useState прописан. Если вы сделаете что-то типа private someState = new UseState(selector, 0) внутри класса, то он так же выведет


                  а также тип и значение находяться в непосредственной близости без загромождения кода:

                  Это как раз в итоге загромождает код рендер-функции.


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

                  Так смысл в том что это не "хуки хорошие", а "поленились нормально сделать в классах". Ну т.е. немного не то позиционирование. Хотя результат один, конечно.


                  А что из жизненного цикла PC или FC не относиться(относиться) к языковой инфраструктуре? Не совсем понятен аргумент вообще.

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


                  А можно пример желаемого синтаксиса, что-то не приходит мыслей о том как это может выглядеть.

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


                  Почему?

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


              1. hd_keeper
                26.09.2019 14:52

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


                1. Koneru
                  26.09.2019 15:47

                  Нет. Прочитайте что такое: чистая функция.


                  1. hd_keeper
                    26.09.2019 16:13
                    +2

                    Прочитал. Всё равно нет, так как хук useState убивает детерминированность функции. А хуки useEffect могут добавить побочные эффекты.

                    Теперь предлагаю пойти почитать вам.


                    1. Koneru
                      26.09.2019 20:09

                      Окей, начну с тезиса.
                      "— Каждый раз функция возвращает одинаковый результат, когда она вызывается с тем же набором аргументов

                      — Нет побочных эффектов"
                      habr.com/ru/post/437512

                      В комментариях почти тоже самое, поэтому будем отталкиваться от этого.
                      Подключение dispatch к любому компоненту уже противоречит понятию чистая функция, поэтому я не рассматривал это. И вообще компонент это уже про работу с DOM, поэтому вообще абсурд. Трактую вашу фразу, хуки загрязняют FC.

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

                      Усложняем, добавляем useState(). Вызывая функцию мы получаем… новый экземпляр компонента. Мы можем множество раз его создать, результат один, init state компонента. Затем делаем Synthetic event, например, клик. То есть, мы создадим некое событие, если это событие произвести с любым экземпляром компонента будет один результат, который не повлияет на внешний скоуп, я только вернёт новой состояние, не будем углубляться в VDOM, но это не повлияет ни на что. Например, каррированная функция суммы, тоже является чистой(в идеальном мире).

                      useEffect уже сложнее, ведь это даёт возможность сделать запрос на сервер, что ДЕЙСТВИТЕЛЬНО УБИРАЕТ СТАТУС ЧИСТОЙ, но не обязывает этого делать, и тогда ситуация также самая что и с useState.

                      IMHO: это уже перебор и разговор зашёл в непродуктивное русло.

                      P.s. комментарии больше статьи, спасибо Druu, хорошая дискуссия.


                      1. faiwer
                        26.09.2019 21:05

                        Вы настолько обобщённо подходите к понятию чистая и грязная функция, что имхо это всё вообще теряет всякий смысл. Это уже какая-то философия начинается :)


                        Если же подходить к вопросу более прямолинейно, то:


                        const A = () => <>I am pure</>; // pure
                        
                        const B = () => {
                          const onClick = () => { kill_all_humans(); }
                          return <button onClick={onClick}>kill</button>
                        }; // B itself is pure, but <B/> is "impure" somehow
                        
                        const C = () => {
                          useAnyKindOfHook(); // impure
                          return null;
                        }; // impure

                        В случае А мы имеем дело с чистой функцией, т.к. она детерминирована и не имеет сайд-эфектов. Про компонент который из неё можно получить я молчу, об этом позже.


                        В случае Б мы снова имеем с чистой функцией, хотя если попытаться воспользоваться продуктом её результата как компонентом, то такой компонент получится impure. Тут уже отдельная песня что считать чистым компонентом, а что нет, т.к. на ФП это натянуть уже не получится, это ведь императивный реакт как IoC вступает в дело и творит что хочет. Стало быть можно придумать какие угодно правила чистоты рас… компонент, либо считать всё грязным всегда вообще (логично, чо).


                        Метод С разумеется сразу impure, т.е. любой хук хранит стейт вне вызывающего его метода. Ни о какой чистоте тут и речи быть не может.


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


                      1. Druu
                        27.09.2019 09:31
                        +2

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

                        Так в то и дело, что с useState это не так! ф-компонент возвращает кусок в-дома. Стейт не является аргументов ф-компонента. С-но, если бы ф-компонент возвращал один и тот же результат, то он бы возвращал один и тот же кусок вдома при разных стейтах (в результате чего смысла от такого стейта бы не было никакого). Но он возвращает разный. Хотя аргументы (пропсы) не меняются.
                        Вот если бы стейт был аргументом — тут другое дело. Но он не аргумент.


                  1. faiwer
                    26.09.2019 17:13
                    +1

                    Что вы имели ввиду под "нет"? Любой хук делает вашу функцию impure.


        1. andres_kovalev
          28.09.2019 03:44

          Да уж, особенно useCallback() появился от большого удобства. Не то, чтобы я не любил или не использовал хуки, просто такое ощущение, что люди стараюся пихать их везде из страха быть не модными. Одни статьи про "Redux на хуках" чего стоят) Нужно ведь понимать, зачем и как работает инструмент, чтобы им пользоваться.


      1. LEXA_JA
        26.09.2019 20:44

        Одно из ключевых преимуществ хуков перед классами это возможность создавать переиспользуемые и комбинируемые куски логики. Хуки являются однозначно более удобным решением чем Render Props, и нередко более удобны, чем HOC'и. Именно возможность удобно создавать кастомные хуки является самой мощной фичей хуков. Если честно, я совсем не против API классов, но там была проблемы именно с выделением кусков логики и привязки к конкретному компоненту. Я не совсем представляю как можно решить именно эту проблему через API классов.


        1. faiwer
          26.09.2019 21:09

          Druu может вам легко возразить на это тем, что концепция классов никак не запрещает группирование единых кусков кода во что-то одно. Это trait-ы, mixin-ы и как их там ещё только не называли. По сути примеси. Было бы желание у создателей React остаться в концепии классов, вернули бы mixin-ы (они когда-то были) и вообще добавили бы много всякого удобного сахара. Но там совсем другой курс партии и работа в рамках ООП им вообще не интересна.


          Т.е. если сказать более корректно, то это не преимущество хуков перед классами, а преимущество хуков перед конкретной классовой моделью React.


          1. LEXA_JA
            26.09.2019 22:53

            Ну у миксинов были проблемы с конфликтами имен и неявными зависимостями между миксинами, поэтому от них отказались в пользу HOC'ов. Еще из плюсов для хуков это возможность подключить один хук несколько раз. Вообще говоря классы в React тоже имеют мало общего с ООП, а о наследовании компонентов вообще упоминать не принято.
            Не буду спорить с тем, что принципиально возможно сделать нормальный API и для классов, но в текущей реализации хуки удобнее классов.


            1. mayorovp
              27.09.2019 09:24

              Проблемы со сложностью конкретного класса в ООП вполне разрешимы и решаются дальнейшей декомпозицией. Миксины усложняют класс? Значит, нужно вынести их в отдельный!


              Всего-то нужно было хранить в каждом компоненте список всех его примесей, а при вызове событий — обходить дерево примесей рекурсивно. Это было бы куда удобнее хуков.


  1. Tonn_Tamerlan
    25.09.2019 15:33

    Ну если уж совсем правильно в случае, когда надо обновить стейт на основании пердыдущего, то лучше вот так:

    this.setState((prevState, props)=>({data: [...prevState.data, item]}))


    1. skoni Автор
      25.09.2019 22:26

      Благодарю за верное замечание. Дополнил статью.


  1. polRk
    25.09.2019 20:14

    Интересно, про первый пункт. Видимо автор не знает, что если нужно обновить поле одного объекта в массиве, придется переписывать весь стейт и все перерисовывать. В данном случае, использование setState — антипатерн. Когда я столкнулся с редактированием динамически сгенерированных карточек, которые содержат по полей пять, setState ложил мое приложение…


    1. zhanbst
      25.09.2019 21:09

      похоже у вас проблемы с функциональным программированием


    1. faiwer
      25.09.2019 21:24

      Не весь и не всё. "Всё сложно". Когда в игру вступает иммутабельность туда же вступают всякие shallow comparison проверки и разная хитрая ссылочная мемоизация. И получается что некоторый оверхед всё же остаётся, но если приложение построено правильно, то обычно он незначительный.


  1. ertaquo
    26.09.2019 07:19

    Дело в том, что никто не гарантирует, что за время прошедшее между получением переменной data из state и записью ее нового значения в state, само состояние не изменится.

    ЕМНИП, Javascript в браузере — синхронный, и ничего внезапно, во время выполнения участка кода, поменяться на может.


    1. Druu
      26.09.2019 07:51
      +1

      setState асинхронный сам по себе. Строчка this.setState(...) не меняет стейт, а говорит реакту запланировать апдейт стейта. По факту заапдейтится он при этом может когда угодно.


      1. andres_kovalev
        28.09.2019 03:39

        Однако, это не всегда правда. В данном коде стейт будет установлен синхронно, более того, дважды вызовет рендер компонента за время работы функции:


        const onClick = () => setTimeout(() => {
          setState(1);
          setState(2);
        }, 0);
        
        return (
          &lt;button onClick={ onClick } /&gt;
        );

        (пишу с мобильного, поэтому возможны небольшие ошибки, но общий смысл должен быть понятен)


        1. Druu
          28.09.2019 06:10

          В данном коде стейт будет установлен синхронно

          Нет. Хук setState просто кладет редьюсер в список. Исполняется этот список уже на другом этапе. Т.е. редьюсер setState(2) будет помещен в список до того, как будет применен редьюсер setState(1).


          А количество рендеров в хуках вообще штука сложная — т.к. исполнение хуков может добавлять хуки :) то у них там просто while-цикл который рендерит компонент до тех пор, пока хуков не останется :)
          Ну и еще вкорячено ограничение на максимальное количество рендеров после которого цикл обрывается. 20 штук, что ли.


    1. skoni Автор
      26.09.2019 07:55

      seState() не изменяет состояние компонента мгновенно после вызова, а отправляет запрос на изменение состояния в очередь. Для повышения производительности React может откладывать на некоторое время обновление состояния, а затем выполнить несколько изменений за раз. Поэтому существует вероятность попадания в очередь двух и более запросов на обновление значения одной и той же переменной. Соответственно, после выполнения первого запроса состояние изменится, но второй запрос никак на это не отреагирует и обновит состояние без учета изменений.


  1. VolCh
    27.09.2019 00:42

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


    1. mayorovp
      27.09.2019 09:26

      Осталось понять как обрабатывать это самое "пользовательское событие".


      1. VolCh
        27.09.2019 09:37

        обработчики popstate event и т. п.


        1. mayorovp
          27.09.2019 09:43

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


          1. VolCh
            27.09.2019 09:54

            мы же псевдо MPA делаем, значит должны быть записаны


            1. mayorovp
              27.09.2019 09:58

              Ну так история-то частью стейта не является (в понимании react и redux)


              1. VolCh
                27.09.2019 10:24

                Поинт в том, чтобы сделать её таковой. Вернее браузерную историю синхронизировать со стейтом (redux,mobx,react, ...), где источник правды для приложения в стейте, изменяется история через принятые способы изменения, а вся работа с браузерным API локализована в одном месте, подписанном на изменения стейта и генерящим actions для него в случае действий пользователя