Почему я разочаровался в хуках

Original author: Paul Cowan
  • Translation
Перевод статьи подготовлен в преддверии старта курса «React.js Developer».





Чем полезны хуки?


Прежде чем я расскажу, в чем и почему разочаровался, я хочу официально заявить, что, вообще-то, я фанат хуков.

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

Хуки — нововведение в React 16.8, которое позволяет использовать состояние и другие возможности React без написания классов.

Посыл, который я здесь вижу, звучит примерно так: «Классы — это не круто!». Маловато, чтобы мотивировать на использование хуков. На мой взгляд, хуки позволяют решать вопросы сквозной функциональности более элегантно, чем прежние подходы: миксины, компоненты высшего порядка и рендер-пропсы.

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

А что не так с классовыми компонентами?


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

export const Heading: React.FC<HeadingProps> = ({ level, className, tabIndex, children, ...rest }) => {
  const Tag = `h${level}` as Taggable;

  return (
    <Tag className={cs(className)} {...rest} tabIndex={tabIndex}>
      {children}
    </Tag>
  );
};

К сожалению, отсутствие побочных эффектов ограничивает возможности использования компонентов без внутреннего состояния. В конце концов, без манипулирования состоянием не обойтись. В React это означает, что побочные эффекты добавляются к классовым компонентам, которые имеют состояние (stateful-компоненты). Их еще называют компонентами-контейнерами. Они выполняют побочные эффекты и передают пропсы чистым функциям — stateless-компонентам.

Хорошо известно о некоторых проблемах, связанных с основанными на классах событиями жизненного цикла. Многие недовольны тем, что приходится повторять логику в методах componentDidMount и componentDidUpdate.

async componentDidMount() {
  const response = await get(`/users`);
  this.setState({ users: response.data });
};

async componentDidUpdate(prevProps) {
  if (prevProps.resource !== this.props.resource) {
    const response = await get(`/users`);
    this.setState({ users: response.data });
  }
};

Рано или поздно все разработчики сталкиваются с этой проблемой.

Этот код побочного эффекта можно выполнить в одном компоненте с помощью хука эффекта.

const UsersContainer: React.FC = () => {
  const [ users, setUsers ] = useState([]);
  const [ showDetails, setShowDetails ] = useState(false);

 const fetchUsers = async () => {
   const response = await get('/users');
   setUsers(response.data);
 };

 useEffect( () => {
    fetchUsers(users)
  }, [ users ]
 );

 // etc.

Хук useEffect заметно облегчает жизнь, но он лишает той чистой функции — stateless-компонента, — которой мы пользовались раньше. Это первое, что меня разочаровало.

Очередная парадигма JavaScript, которую нужно знать


Мне 49 лет, и я фанат React. После разработки приложения на ember с этим безумием в виде наблюдателей и вычисляемых свойств я всегда буду питать теплые чувства к однонаправленному потоку данных.

Проблема с хуком useEffect и ему подобными в том, что на всем ландшафте JavaScript он больше нигде не используется. Он необычный и вообще со странностями. Я вижу только один способ укротить его — использовать этот хук на практике и страдать. И никакие примеры со счетчиками не побудят меня самозабвенно кодить ночь напролет. Я фрилансер и пользуюсь не только React, но и другими библиотеками, и я уже устал следить за всеми этими нововведениями. Стоит только подумать, что нужно установить плагин eslint, который наставит меня на путь истинный, как эта новая парадигма начинает меня напрягать.

Массивы зависимостей — это ад


Хук useEffect может принимать необязательный второй аргумент, который называется массивом зависимостей и позволяет выполнять обратный вызов эффекта, когда это нужно вам. Чтобы определить наличие изменений, React сравнивает значения друг с другом методом Object.is. Если какие-то элементы изменились с момента последнего цикла рендеринга, то эффект будет применяться к новым значениям.

Сравнение отлично подходит для обработки примитивных типов данных. Но если один из элементов является объектом или массивом, могут возникнуть проблемы. Object.is сравнивает объекты и массивы по ссылке, и ничего с этим не сделаешь. Пользовательский алгоритм сравнения применить не удастся.

Проверка объектов по ссылке — это известный камень преткновения. Рассмотрим упрощенный вариант проблемы, с которой я недавно столкнулся.

const useFetch = (config: ApiOptions) => {
  const  [data, setData] = useState(null);

  useEffect(() => {
    const { url, skip, take } = config;
    const resource = `${url}?$skip=${skip}&take=${take}`;
    axios({ url: resource }).then(response => setData(response.data));
  }, [config]); // <-- will fetch on each render

  return data;
};

const App: React.FC = () => {
  const data = useFetch({ url: "/users", take: 10, skip: 0 });
  return <div>{data.map(d => <div>{d})}</div>;
};

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

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

Есть плагин eslint для автоматического исправления ошибок, обнаруженных при валидации кода. Он подходит для любых текстовых редакторов. Честно говоря, меня напрягают все эти новые фичи, для проверки которых нужно устанавливать внешний плагин.

Само существование таких плагинов, как use-deep-object-compare и use-memo-one, говорит о том, что проблема (или по крайней мере неразбериха) действительно есть.

React полагается на порядок вызова хуков


Самыми первыми пользовательскими хуками были несколько реализаций функции useFetch для запросов к удаленному API. Большинство из них не решают проблему, связанную с выполнением запросов к удаленному API из обработчика событий, потому что хуки можно использовать только в начале функционального компонента.

Но что если в данных есть ссылки на сайты с пагинацией, и мы хотим перезапустить эффект, когда пользователь щелкнет по ссылке? Вот простой пример использования useFetch:

const useFetch = (config: ApiOptions): [User[], boolean] => {
  const [data, setData] = useState<User[]>([]);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    const { skip, take } = config;

    api({ skip, take }).then(response => {
      setData(response);
      setLoading(false);
    });
  }, [config]);

  return [data, loading];
};

const App: React.FC = () => {
  const [currentPage, setCurrentPage] = useState<ApiOptions>({
    take: 10,
    skip: 0
  });

  const [users, loading] = useFetch(currentPage);

  if (loading) {
    return <div>loading....</div>;
  }

  return (
    <>
      {users.map((u: User) => (
        <div>{u.name}</div>
      ))}
      <ul>
        {[...Array(4).keys()].map((n: number) => (
          <li>
            <button onClick={() => console.log('что дальше?')}>{n + 1}</button>
          </li>
        ))}
      </ul>
    </>
  );
};

В строке 23 хук useFetch будет вызван один раз при первом рендере. В строках 35–38 мы рендерим кнопки пагинации. Но как мы бы вызвали хук useFetch из обработчика событий для этих кнопок?

В правилах хуков четко написано:

Не используйте хуки внутри циклов, условных операторов или вложенных функций.Вместо этого всегда используйте хуки только на верхнем уровне React-функций.

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

Вот так делать нельзя:

<button onClick={() => useFetch({ skip: n + 1 * 10, take: 10 })}>
  {n + 1}
</button>

Вызов хука useFetch из обработчика событий нарушает правила хуков, потому что меняется порядок их вызова при каждом рендере.

Возврат исполняемой функции из хука


Я знаком с двумя решениями этой проблемы. Они опираются на один и тот же подход, и мне нравятся оба. Плагин react-async-hook возвращает из хука функцию execute:

import { useAsyncCallback } from 'react-async-hook';

const AppButton = ({ onClick, children }) => {
  const asyncOnClick = useAsyncCallback(onClick);
  return (
    <button onClick={asyncOnClick.execute} disabled={asyncOnClick.loading}>
      {asyncOnClick.loading ? '...' : children}
    </button>
  );
};

const CreateTodoButton = () => (
  <AppButton
    onClick={async () => {
      await createTodoAPI('new todo text');
    }}
  >
    Create Todo
  </AppButton>
);

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

React-hooks-async — это плагин с похожим подходом. В нем используется функция useAsyncTask.

Вот полный пример с упрощенной версией useAsyncTask:

const createTask = (func, forceUpdateRef) => {
  const task = {
    start: async (...args) => {
      task.loading = true;
      task.result = null;
      forceUpdateRef.current(func);
      try {
        task.result = await func(...args);
      } catch (e) {
        task.error = e;
      }
      task.loading = false;
      forceUpdateRef.current(func);
    },
    loading: false,
    result: null,
    error: undefined
  };
  return task;
};

export const useAsyncTask = (func) => {
  const forceUpdate = useForceUpdate();
  const forceUpdateRef = useRef(forceUpdate);
  const task = useMemo(() => createTask(func, forceUpdateRef), [func]);

  useEffect(() => {
    forceUpdateRef.current = f => {
      if (f === func) {
        forceUpdate({});
      }
    };
    const cleanup = () => {
      forceUpdateRef.current = () => null;
    };
    return cleanup;
  }, [func, forceUpdate]);

  return useMemo(
    () => ({
      start: task.start,
      loading: task.loading,
      error: task.error,
      result: task.result
    }),
    [task.start, task.loading, task.error, task.result]
  );
};

Функция createTask возвращает объект «задание» в следующем виде.

interface Task {
  start: (...args: any[]) => Promise<void>;
  loading: boolean;
  result: null;
  error: undefined;
}

У задания есть состояния загрузка, ошибка и результат, которые мы ожидаем. Но функция возвращает еще и функцию start, которую можно вызвать позже. Задание, созданное с помощью функции createTask, не влияет на обновление. Обновление запускается функциями forceUpdate и forceUpdateRef в useAsyncTask.

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

Но мы потеряли возможность вызвать хук при первом запуске функционального компонента. Хорошо, что плагин react-hooks-async содержит функцию useAsyncRun — это облегчает задачу:

export const useAsyncRun = (
  asyncTask: ReturnType<typeof useAsyncTask>,
  ...args: any[]
) => {
  const { start } = asyncTask;
  useEffect(() => {
    start(...args);
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [asyncTask.start, ...args]);
  useEffect(() => {
    const cleanup = () => {
      // тут делаем сброс
    };
    return cleanup;
  });
};

Функция start будет выполняться при изменении любого из аргументов args. Теперь код с хуками выглядит так:

const App: React.FC = () => {
  const asyncTask = useFetch(initialPage);
  useAsyncRun(asyncTask);

  const { start, loading, result: users } = asyncTask;

  if (loading) {
    return <div>loading....</div>;
  }

  return (
    <>
      {(users || []).map((u: User) => (
        <div>{u.name}</div>
      ))}

      <ul>
        {[...Array(4).keys()].map((n: number) => (
          <li key={n}>
            <button onClick={() => start({ skip: 10 * n, take: 10 })}>
              {n + 1}
            </button>
          </li>
        ))}
      </ul>
    </>
  );
};

Согласно правилам хуков, мы используем хук useFetch в начале функционального компонента. Функция useAsyncRun вызывает API в самом начале, а функцию start мы используем в обработчике onClick для кнопок пагинации.

Теперь хук useFetch можно использовать по назначению, но, к сожалению, приходится идти обходными путями. Мы также используем замыкание, которое, надо признаться, меня слегка пугает.

Контроль хуков в прикладных программах


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



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

LogRocket записывает все действия и состояния из хранилища Redux. Это набор инструментов для вашего приложения, которые позволяют записывать запросы/ответы с заголовками и телами. Они записывают HTML и CSS на странице, обеспечивая попиксельное воспроизведение даже для самых сложных одностраничных приложений.

LogRocket предлагает современный подход к отладке React-приложений — попробуйте бесплатно.

Заключение


Думаю, что пример с useFetch лучше всего объясняет, почему я разочарован хуками.

Добиться нужного результата оказалось не так просто, как я ожидал, но все же мне понятно, почему так важно использовать хуки в определенном порядке. К сожалению, наши возможности сильно ограничены из-за того, что хуки можно вызвать только в начале функционального компонента, и придется дальше искать обходные пути. Решение с useFetch довольно сложное. К тому же при использовании хуков не обойтись без замыканий. Замыкания — это сплошные сюрпризы, которые оставили в моей душе много шрамов.

Замыкания (вроде тех, что передаются в useEffect и useCallback) могут захватить старые версии пропсов и значения состояний. Это случается, например, когда во входном массиве по какой-то причине отсутствует одна из захваченных переменных — могут возникнуть сложности.

Устаревшее состояние, которое возникает после выполнения кода в замыкании, — одна из проблем, которую призван решить линтер хуков. На Stack Overflow накопилось много вопросов об устаревшем состоянии в хуке useEffect и подобных. Я обертывал функции в useCallback и крутил массивы зависимостей и так, и эдак, чтобы избавиться от проблемы с устаревшим состоянием или бесконечным повторением рендера. Иначе нельзя, но это слегка раздражает. Это реальная проблема, которую приходится решать, чтобы доказать, чего ты стоишь.

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

Надеюсь, я просто недопонял этот подход. Если это так, напишите об этом в комментариях.


Читать ещё:


OTUS
Цифровые навыки от ведущих экспертов

Comments 24

    –1

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


    Проблема с сравнением по ссылке тоже яйца выеденного не стоит: автор же сам разлагает config на url, skip и take, так в чём проблема их в массив зависимостей и засунуть? Вода сплошная для того, чтобы статья внушительнее выглядела.


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

      +9

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


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


      Я работал над проектов в котором хуки сыграли злую роль, из-за этих пропавших зависимостей и из-за того, что нет контроля над мемоизацией, а архитектурных навыков у начинающих разработчиков мало, встречались компоненты с 5-8 useEffect, 2-3 useState в одном компоненте, который перерендеривался по 20 раз. Включив подсветку обновлений в React devtolls (которую убрали в одном из обновлений sic!, потом вернули), приложение было похоже на новогоднюю гирлянду.


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


      Хуки выглядят просто, а на деле — очень опасные и коварные стейт-машины.

        +2
        Вы пишете так, как будто в этом хуки виноваты. И как будто с классовыми компонентами в реакте очень сложно не по делу перерисовывать компоненты (нет).

        Я видел немаленьких размеров UI на реакте, который полностью на 100% перерисовывал себя во всех без исключениях ситуациях (даже если по нему просто мышкой поводить, срабатывали onmousemove). Хуков там было использовано 0 — в те времена о них только еще объявили.
          0
          Вы пишете так, как будто в этом хуки виноваты.
          Именно так я и пишу. Этот инструмент более требователен к учидчивости, пониманию внутренних концептов, нежели Реакт без хуков.

          С хуками проще сделать неподдерживаемое приложение.


          Из опыта нескольких SPA с хуками/без и нескольких разработчиков разного уровня.

            +2
            С хуками проще сделать неподдерживаемое приложение.

            Это пустая риторика. Да, хуки представляют меньше «архитектурных границ». Нет, наличие любого количества архитектурных границ не мешает писать говнокод. Есть ли статистическая зависимость одного от другого? Не знаю, и не думаю, что кто-то проводил хорошие исследования на этот счёт. Всё, что я тут вижу — это голословные утверждения, которые «интуитивно» похожи на правду, и которые часто выдаются за реальное положение вещей через тезисы в духе «общеизвестно, что на C++ говнокодить проще, чем на яве» (или наоборот, в зависимости от того, что нравится и не нравится человеку, это высказывающему).

            Меж тем, мой личный опыт говорит мне о том, что говнокодят всегда и везде, вне зависимости от выбранных инструментов. Например, уже несколько лет я сталкиваюсь с говноприложениями на react+redux, в то время как публичный дискурс мне постоянно утверждает, что редакс — это enterprise-grade, куча рамок, и минимизация возможностей «неокрепших джуниоров» чего-нибудь напортачить. А потом меня зовут на очередной нерасширяемый или погребенный под проблемами производительности проект и просят что-то сделать (что обычно превращается в классическое «все переписать»).
              0

              Все верно, в редаксе как раз очень мало архитектурных границ.
              Вот, redux-toolkit, надеюсь, может чуть улучшить ситуацию.


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

                +1
                Мой опыт — хорошие программисты делают нормально с любыми инструментами (потому что сами их выбирают сообразно задачам), плохие — делают плохо с любыми (в том числе и выбранными за них хорошими). Работу плохих программистов можно использовать тогда, когда за её качеством приглядывают хорошие. Если плохих программистов отправить самостоятельно резвиться в каких угодно жестких архитектурных рамках — результат всё равно будет плачевен.
          –1

          Ну не разрешайте использовать useEffect людям, которые не умеют с ним работать, в чем проблема?

        +5

        Хоть кто-то правду написал!

          –1

          Поводу о чём? Не нравятся хуки работайте с классами, как будто Вам запретили из использовать.

            0
            О хуках.
              0

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

                –1
                Документацию я прочитал и сразу понял, что хуки — это бред. Но тут поднялся дичайший хайп на тему хуков, и я уже начал сомневаться: кто же тут безумен, я или все эти хайпожоры. А теперь последние сомнения отпали.
                  0

                  Одна рекламная статейка развенчала сомнения? Около года с хуками работаю и всё гораздо удобнее, компактнее, нежели с классами. А недостатки есть и там и там.

          –1
          Возврат исполняемой функции из хука

          Зачем?
            +2

            Ну вот, кто-то начинает триггерить фазу "освобождение от иллюзий" по хукам. По поводу "В текущем ландшафте JavaScript нет ничего подобного" — хук useEffect как паттерн это стандартная асинхронщина через event loop:


            let prevMyProp = null;
            
            function Component({ myProp }) {
              setTimeout(() => {
                if (prevMyProp === myProp) return;
            
                1 + 1;
            
                 prevMyProp = myProp;
              }, Component.didRenderTimeout) // по большей части 0
            
              // то же самое хуком
              React.useEffect(() => {
                1 + 1;
              }, [myProp])
            
              return <div />
            }

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

              0
              о которых можно узнать из этого замечательного поста.

              По моему здесь упущена ссылка на ресурс.
                0

                за вычетом двух проблем — порядок хуков и их количество должно соблюдаться и передача объектов в массив зависимостей — все остальное — это by design и в общем-то плюс.
                Первая проблема нерешаема вообще но вобщем-то терпима и ничего страшного за собой не несет;
                Вторая проблема решается (или подталкивает к решению) через оптимизацию зависимостей и понимание того откуда приходят эти зависимости, и решиться окончательно когда завезут поддержку tuple и record — массивы и объекты которые сравниваются по значению а не по референсу;
                При этом хуки несут:
                1) инкапсуляцию логики; теперь любую сложную логику можно хранить в одном месте и видеть порядок выполнения;
                2) более простую типизацию, хуки принимают и отдают (как правило) легко типизируемые структуры данных;
                3) более простую логическую модель. Не нужно знать о конкретных компонентах и их lifecycle, достаточно знать на самом деле что есть хуки для хранения состояния (useState, и useRef) и для вызова эффектов (useEffect/useLayoutEffect). Все остальное — композиция этих двух типов хуков;
                4) легковесность. Хуки прекрасно выносятся в пакеты и живут потом в react/react-native проектах;
                5) оптимизация и дебаг. Если у вас тысяча и один рендер вызываются на каждый чих то гораздо проще локализовать источник проблемы и оптимизировать хук чем продираться через древо компонентов.


                Являются ли хуки серебряной пулей? нет конечно, ни один из инструментов/паттернов не является. Да, асинхронные операции чуть более боль чем хотелось бы и массив зависимостей может выстрелить в ногу при невнимательности.
                К тому же хуки абсолютно опционально, и если бы комьюнити (ну или у вас в конкретном проекте) решили что классы это то что вам нужно — никто не запрещает продолжать пользоваться ими.

                  0
                  Не соглашусь. Опициональность в контексте речи о Реакте подразумевает заменяемость на классы. Хуки уже не заменяемы в некоторых случаях habr.com/ru/company/yandex/blog/514016/#comment_21933114
                    0

                    совершенно забыл про transition хуки. Да, небольшой дескреп все же есть (ErrorBoundary только классы, а некоторые фичи suspense — только хуками) но как правило эти элементы абстрагированы в библиотеки.
                    По большому счету можно продолжать использовать классы.

                  –3
                  Проблема не в хуках, а в реакте. Ни у одного другого фреймворка нет столько статей о том, как выполнить простое действие — управлять данными программы. Всё потому, что его авторы раньше никогда не писали фронтенд и попытались натянуть подходы бекенда на среду с другими требованиями и характеристиками. Отсюда и их постоянные смены парадигм и изменение подходов, они поняли, что что-то делали не так, но не хотят признавать ошибки. Они ссылаются на научные статьи, чтобы описать маленький кусок логики, но это бронированная дверь, приставленная к деревянному сараю. Концентрируясь на малом они упускают большое. После хуков будет ещё одно решение, потом может быть ещё несколько, пока они не придут к подходу, давно используемому в отрасли и применяющемуся в MobX (костылю, позволяющему реакту ходить), Angular и Vue.
                    +3
                    При чём тут Angular или Vue, чем они лучше реакта?
                      +1
                      Работой со стейтом. У них изменения трекаются в момент выполнения, в реакте — в момент рендера. В результате в реакт мы имеем быстрый первый рендер, но вся последующая работа с программой будет тормозить гораздо сильнее, чем при общепринятом подходе. Если объяснять на пальцах, то angular, vue и остальные записывают дифф изменений стейта и при следующем рендере просто его применяют, это очень быстрая операция. Реакт ничего не записывает, а вместо этого сравнивает дерево пропсов заново при каждом рендере, в этом случае время выполнения растёт нелинейно при росте числа компонентов и свойств (O(N^X), где X > 1). Лет 20 назад разработчиков за такое увольняли, но времена сейчас не те, ассимптотическую сложность не оценивают и половина даже не понимает. Общепринятый подход с трекингом изменений даёт линейный рост (X=1), то есть с ним можно создавать программы любого размера. Реакт же хорошо работает на маленьких модельных примерах, в синтетических бенчмарках, но когда приложение становится достаточно большим — начинаются тормоза, и чтобы их избежать начинаются ухищрения, сильно снижающие developer experience. Так например иммутабельность в redux нужна не из за того, что это хорошо, а чтобы помогать реакту проводить сравнение дерева свойств. Без неё он просто захлебнётся. Вас не спрашивают, хотите вы иммутабельность или нет, это необходимый довесок, потому что без этого пересчёт дерева будет занимать слишком много времени. Они оправдали собственные недоработки философией, что якобы так правильнее, ФП, все дела, хотя настоящая причина — что это один из немногих способов заставить реакт сносно работать в больших приложениях. Другой вариант — прописывать componentShouldUpdate в каждом компоненте, чтобы по-умному трекать изменения в дереве, но это настолько утомительно, что никто этим не занимается. С общепринятым же подходом, используемым в angular, vue и куче других библиотек, изменения трекаются в момент внесения и дерево сравнивать не нужно. Это похоже на индекс в базе данных. Вы просто сохраняете то, что вам будет нужно уже через несколько миллисекунд, чтобы не вычислять это по всему объёму данных в момент рендера. Это же так логично. Как результат, не нужно коверкать developer experience, вы можете просто присвоить значение стейту, можете мутировать, можете вынести во внешний модуль, можете добавлять к данным методы для их обработки и нативные объекты вроде Map, Set и т.п… В реакт это считается антипаттерном, потому что реакт не может это съесть, ему нужно разжевать и положить в рот. Вот, для сравнения, как стейт может создаваться во vue:
                      const state = {};
                      

                      Это то, что в мире реакта называют «магией», потому что они не поняли, как это работает. И эта одна строчка на самом деле всё, что вам нужно, чтобы работать с данными в общепринятом подходе. Вы можете импортировать его в компоненты, вы можете изменять его как угодно и все изменения будут автоматически отражены в визуальной части. Именно поэтому про работу с данными во Vue и не пишут статей на хабре. Что о них писать, если они просто работают, надёжно и интуитивно.
                      0
                      > с другими требованиями и характеристиками.

                      С какими?

                    Only users with full accounts can post comments. Log in, please.