React Redux: больше контейнеров против. меньше контейнеров

По мере того, как я продвигаюсь в реализацию redux +, реагирую на довольно сложное приложение, которое зависит от многих запросов API для загрузки одной страницы, у меня возникают проблемы с решением, нужно ли иметь один контейнерный компонент в корне страницы который обрабатывает все асинхронные вещи и передает реквизиты до немых компонентов, vs имеющих несколько компонентов контейнера, которые относятся только к необходимым им данным, а также к сбору данных, которые им нужны. Я пошел туда и обратно между этими двумя шаблонами и обнаружил, что у каждого из них есть плюсы и минусы:

Если я поставлю один компонент контейнера вверху:

  • pro: Все действия isFetching реквизита и fetchSomeDependency() могут обрабатываться в одном месте.
  • con: недостаток, который действительно раздражает, заключается в том, что мне приходится пересылать реквизиты и обратные вызовы через несколько компонентов, а некоторые компоненты в середине дерева в конечном итоге привязаны к этому.

Здесь наглядный пример проблемы, которая показывает отношения, требуемые реквизитом:

<MyContainer
  data={this.props.data}
  isFetchingData={this.props.isFetchingData}
  fetchData={this.props.fetchData}
 >
   {!isFetchingData && 
     <MyModal
        someData={props.data}
        fetchData={props.fetchData}
      >
       <MyModalContent
         someData={props.data}
         fetchData={props.fetchData}
       >
          <SomethingThatDependsOnData someData={props.someData} />
          <SomeButtonThatFetchesData  onClick={props.fetchData} />
        </MyModalContent>
      </MyModal>
    }
 </MyContainer>

Как вы можете видеть, <MyModal /> и <MyModalContent /> теперь нужно иметь дело с реквизитами, которые не имеют к этому никакого отношения, поскольку модальность должна быть в состоянии повторно использоваться и учитывать только стилистические качества модальный.

Вначале это выглядело здорово, но как только я добрался до более 100 компонентов, все это было очень запутанным, и я обнаружил, что сложность этих компонентов контейнера верхнего уровня слишком высока по моему вкусу, поскольку большинство из них (в приложение, над которым я работаю) зависят от ответов от запросов API 3+.

Затем я решил попробовать несколько контейнеров:

  • pro: Полностью устраняет необходимость перенаправления реквизита. В некоторых случаях это имеет смысл сделать, но это намного более гибко.
  • pro: Упрощение рефакторинга. Я удивлен тем, как я могу значительно перемещаться и реорганизовать компоненты без каких-либо взломов, тогда как в другом шаблоне все ломается много.
  • pro: Сложность каждого компонента контейнера намного меньше. Мои mapStateToProps и mapDispatchToProps более специфичны для цели компонента, в котором он находится.
  • con: Любой компонент, зависящий от материала async, всегда должен обрабатывать состояние isFetching сам по себе. Это добавляет сложности, которые не нужны в шаблоне, где его обрабатывают в одном компоненте контейнера.

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

Я хотел бы знать, нашел ли кто-нибудь способ обойти оба недостатка, и если да, то каково "правило большого пальца", которое вы следуете, чтобы избежать этого?

Спасибо!

Ответ 1

Я всегда видел, что ваши контейнеры находятся в верхней части большинства компонентов группы логических компонентов, отличных от вашего компонента root/app.

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

<Root> <- setup the app
  <App>
    <Search/> <- search input
    <Results/> <- results table
  </App>
</Root>

Я бы сделал контейнеры Search и Results redux. Потому что компонент реакции должен быть композиционным. У вас могут быть другие компоненты или страницы, на которые нужно отображать Results или Search. Если вы делегируете сбор данных и сохраняете информацию в корневом или компонентном приложении, это заставляет компоненты становиться зависимыми друг от друга/приложение. Это усложняет работу, когда вам придется внедрять изменения, теперь вам нужно изменить все места, которые их используют.

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

Ответ 2

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

Я думаю, что есть тонкая линия для ходьбы. Я делаю приложение, где я был на нем около 5 недель (неполный рабочий день), и это до 3000 строк прямо сейчас. Он имеет 3 уровня вложенности представлений, механизм маршрутизации, который я реализовал сам, и компоненты, которые имеют более 10 уровней глубины вложенности, которые нуждаются в изменении состояния. У меня в основном есть один контейнер для конвертов для каждого большого экрана, и он работает чудесно.

Однако, если я нахожусь на моем представлении "клиенты", я получаю список клиентов, который прекрасен, так как просмотр моих клиентов находится внутри контейнера redux и получает список клиентов, переданных в качестве реквизитов. Однако, когда я нажимаю на одного клиента, я действительно не решаюсь сделать еще один контейнер redux для отдельного профиля клиента, так как это только один дополнительный уровень прохождения реквизита. Похоже, что в зависимости от объема приложения вы можете передать реквизиты до 1-2 уровней за контейнер redux, и если это не более того, просто создайте еще один контейнер redux. Опять же, если это еще более сложное приложение, то смешивание иногда с использованием контейнеров redux и некоторых других случаев, не использующих их, может быть еще хуже для ремонтопригодности. Короче говоря, мое мнение пытается свести к минимуму контейнеры-редукторы, где это возможно, но определенно не за счет сложных опорных цепей, так как основной смысл использования redux для начала.

Ответ 3

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

Он называет это "продолжающимся процессом рефакторинга".

См. Эту статью: https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

Ответ 4

Так прошло более двух лет с тех пор, как я опубликовал этот вопрос, и все это время я постоянно работал с React/Redux. Мое общее правило состоит в следующем: используйте больше контейнеров, но старайтесь писать компоненты таким образом, чтобы им не нужно было знать об isFetching.

Например, вот типичный пример того, как я бы создал список дел до:

  function Todos({ isFetching, items }) {
    if (isFetching) return <div>Loading...</div>

    return (
      <ul>
        {items.map(item => 
          <li key={item.id}>...</li>
        )}
      </ul>
    )
  }

Теперь я бы сделал что-то большее:

  function Todos({ items }) {
    if (!items.length) return <div>No items!</div>

    return (
      <ul>
        {items.map(item => 
          <li key={item.id}>...</li>
        )}
      </ul>
    )
  }

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

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

  1. Мне нужно предотвратить, например, нажатие кнопки "Отправить" во второй раз, что вызывает вызов API, и в этом случае опору следует, вероятно, называть disableSubmit а не isFetching, или

  2. Я хочу явно показать загрузчик, когда что-то ждет асинхронного ответа.

Теперь вы можете подумать: "Разве вы не хотите показывать загрузчика, когда предметы извлекаются в приведенном выше примере todos?" но на практике я бы этого не сделал.

Причина этого заключается в том, что в приведенном выше примере допустим, что вы проводили опрос для новых todos, или когда вы добавляете todo, вы "обновляете" todos. Что будет в первом примере, так это то, что каждый раз это случалось, тоды исчезали и часто заменялись "Загрузка...".

Однако во втором примере, который не связан с isFetching, новые элементы просто добавляются/удаляются. На мой взгляд, это намного лучше UX.

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