Redux: контейнер против компонента?

Созданный на 19 сент. 2015  ·  46Комментарии  ·  Источник: reduxjs/redux

В примерах всегда есть папка с именем «контейнер» и папка с именем «компонент». Какая мысль стоит за этим разделением?

Являются ли «контейнеры» интеллектуальным компонентом или они являются компонентами маршрутизации или что-то еще? Должны ли компоненты в разделе "компоненты" всегда быть тупыми?

docs question

Самый полезный комментарий

Я называю components инкапсулированные компоненты React, которые управляются исключительно реквизитами и не общаются с Redux. То же, что и «тупые компоненты». Они должны оставаться неизменными независимо от вашего маршрутизатора, библиотеки выборки данных и т. Д.

Я называю компоненты containers React, которые знают о Redux, Router и т. Д. Они больше связаны с приложением. То же, что и «умные компоненты».

Все 46 Комментарий

Что касается меня, container - это обработчик маршрута, который также извлекает состояние redux для этого маршрута. Затем я передаю свое состояние как опору.

Например,

контейнеры / properties.jsx

import React, { Component } from 'react';
import { bindActionCreators } from 'redux';
import { connect }  from 'react-redux';

import * as actions from 'actions/properties';
import Filter from 'components/properties/filter';
import List from 'components/properties/list';
import Pagination from 'components/properties/pagination';

class PropertiesContainer extends Component {
  render() {
    return (
      <section>
        <Filter filter={this.props.properties.params.filter} />
        <List items={this.props.properties.items} isFetching={this.props.properties.isFetching} />
        <Pagination pagination={this.props.properties.params.pagination} />
      </section>
    );
  }
}

function mapState(state) {
  const { properties } = state;

  return { properties };
}

function mapDispatch(dispatch) {
  return {
    actions: bindActionCreators(actions, dispatch),
  };
}

const Connector = connect(mapState, mapDispatch)(PropertiesContainer);

export default Connector;

и, например,

компоненты / свойства / pagination.jsx

import React, { Component } from 'react';
import Pager from 'components/ui/pager';

class Pagination extends Component {
  render() {
    const { total, offset, limit } = this.props.pagination;
    const current = offset / limit;

    return (
      <Pager total={total} current={current} />
    )
  }
}

export default Pagination;

Чтение по предоставленным вами ссылкам:

"A container does data fetching and then renders its corresponding sub-component. "

У меня больше ощущения, что «контейнеры» - это на самом деле то, что в redux называется «умными компонентами» ... и маршруты / страницы должны иметь свою собственную папку.

Я не уверен, почему «контейнер» и «обработчики маршрута» каким-либо образом связаны?

Очень распространена практика группировки компонентов вашего приложения по маршруту. Как отметил @theaqua , также принято делать каждый обработчик / компонент маршрута интеллектуальным / контейнерным компонентом. Если вы используете combReducers, вы часто обнаружите, что разбиваете состояние, маршруты и контейнеры по одним и тем же направлениям. Следовательно, они часто ассоциируются вместе.

@ronag Я не думал, что вам нужно делать контейнеры и страницы. Почему вы не можете поместить обработчик маршрута и соединитель redux в состояние в одном месте? Это очень удобно. Будь проще.

В моем приложении всего 3 маршрута / страницы. С другой стороны, у меня много независимых панелей / модулей. Сделать один огромный connect невозможно, мне нужно хотя бы разделить его на части.

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

Если у вас 3 маршрута, значит, вам нужно 3 обработчика маршрута. Например, A.jsx , B.jsx и C.jsx в /containers .

В каждом контейнере вы извлекаете часть (не всю!) Состояния redux, и действия связываются. Затем вы передаете это компонентам, как в моем примере. Он очень удобен в обслуживании, потому что я (и моя команда) знаю - подключайтесь к redux, и действия всегда связываются в containers , его никогда не будет в components (который маленький и часто не имеет состояния, как в моем примере ).

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

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

Я попробую ваше предложение и посмотрю, чем мы закончим.

Я называю components инкапсулированные компоненты React, которые управляются исключительно реквизитами и не общаются с Redux. То же, что и «тупые компоненты». Они должны оставаться неизменными независимо от вашего маршрутизатора, библиотеки выборки данных и т. Д.

Я называю компоненты containers React, которые знают о Redux, Router и т. Д. Они больше связаны с приложением. То же, что и «умные компоненты».

@gaearon : Отлично. Это проясняет примеры. Спасибо.

@gaearon Итак, "глупые компоненты" - это компоненты без состояния, которые могут быть написаны с использованием более простого синтаксиса, начиная с React 0.14 , например:

var Aquarium = (props) => {
  var fish = getFish(props.species);
  return <Tank>{fish}</Tank>;
};

Я прав?

@soulmachine ага .

Не обязательно. Дело не в синтаксисе.

«Глупые» компоненты, также известные как «презентационные» компоненты, - это компоненты, которые получают все данные с помощью свойств, не знают о Redux и определяют только внешний вид, но не поведение.

@theaqua @gaearon Спасибо!

Хотя термин «контейнер» уже довольно популярен в номенклатуре react / redux, мы решили назвать их «коннекторами» в проекте, над которым я работаю, чтобы избежать путаницы с макетными / графическими контейнерами.

кстати, как их называть ранее обсуждалось здесь rackt / react-redux # 170. Я храню все в папке components а презентационные на один уровень глубже, внутри components/presentational , и я думаю, что это нормально, потому что они вряд ли понадобятся другим частям приложения, кроме контейнеров.

@gaearon так это компонент, который dispatch считается "тупым" или "умным"? Особенно полезно иметь конечный или промежуточный компонент для отправки действия вместо того, чтобы возвращать событие обратно в верхний компонент для отправки.

Да, иногда это может быть полезно, хотя я предпочитаю connect() такие компоненты, чтобы создатель действий вводился как опора. Неважно, как вы это назовете :-)

А что насчет того, когда вы создаете модуль из компонентов? Например, предположим, что у меня есть отдельный модуль узла для нашего меню навигации <NavMenu> Я хочу, чтобы люди выполняли такой код:

import {NavMenu} from 'my-redux-aware-components';

export function myPage(props) {
  return (<div><NavMenu routes={props.routes} /></div>);
}

так что я просто назову его «NavMenuContainer»? мне это кажется странным. Должен ли я вместо этого назвать компонент NavMenuComponent? оба кажутся мне странными. В этом случае компонент вызывает соединение только для получения 1 поля из состояния. Неужели так плохо просто встроить вызов для подключения таким образом?

export default const NavMenu = connect(state => ({currentPath:state.routing.path})(React.createClas({...}));

Любопытно услышать ваши мысли @gaearon о том, когда (если вообще) "нормально" просто встроить вызов подключения ...

Неважно, как вы это называете. Почему бы не назвать это NavMenu ?

Я не понимаю вопроса о встраивании.
Было бы полезно, если бы вы представили два сравниваемых подхода.

хорошо, например.
вариант 1 (2 отдельных файла, 1 для контейнера, 1 для компонента)

контейнеры / NavMenu

import {connect} from 'react-redux';
import NavMenu from '../components/NavMenu';

export default connect(state => ({currentPath:state.routing.path})(NavMenu);

вариант 2 (1 файл, содержащий оба):
компоненты / NavMenu

import {connect} from 'react-redux';

export default connect(state => ({currentPath:state.routing.path})(React.createClass({
  render() {
      return <div>the menu {this.props.currentPath} goes here</div>;
  }
});

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

Конечно, в простых случаях это разумно сделать.

ладно, круто. когда бы вы нарисовали линию и сказали: «Хорошо, я бы переместил это в 2 отдельных файла»?

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

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

спасибо @gaearon

@sompylasar очень хорошее замечание! Благодарность

хм, хорошо, после того, как я провел рефакторинг моего кода, чтобы разделить его, у меня теперь есть следующий вопрос. предположим, что у меня также есть контейнер <NavMenuItem> . Компонент <NavMenu> должен ссылаться на <NavMenuItem /> как на дочерний элемент. Должен ли я просто сделать import NavMenuItem from '../containers/NavMenuItem' в компоненте NavMenu? Как это влияет на тестируемость @sompylasar?

Я бы сделал NavMenuItem чисто презентационным компонентом со всеми необходимыми данными, передаваемыми в него через свойства NavMenu. Это позволило бы протестировать его отдельно. Итак, у вас будет два презентационных (NavMenu, NavMenuItem) и один подключенный (connect (...) (NavMenuItem)) компонент.

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

FWIW Я обновил статью о компонентах презентации и контейнера, чтобы отразить мое текущее мышление.

Мы больше не препятствуем созданию компонентов контейнера в обновленных документах.
http://redux.js.org/docs/basics/UsageWithReact.html

@gaearon в вашем фактическом примере в документе Redux кажется, что компоненты могут иметь контейнеры в качестве дочерних.
Я ошибся? Как это может повлиять на тестируемость глупого компонента, который внутри него отображает умный?
Однако я не могу позволить компонентам быть последними в иерархии ...

У меня есть приложение, которое отображает компонент Simple Data List (Dumb).
Внутри него каждый предмет должен быть подключен к магазину, так что он умный.

Согласно документу это нормально, но может ли это вызвать проблемы?

Благодарить!

Как это может повлиять на тестируемость глупого компонента, который внутри него отображает умный?

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

Ладно, нет правильного пути. Мы должны оценивать индивидуально. Многие
Благодарность!!

Il lunedì 8 febbraio 2016, Dan Abramov [email protected] ha
сценарий:

Как это может повлиять на тестируемость немого компонента, который рендерится внутри него?
умный?

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

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/rackt/redux/issues/756#issuecomment -181143304.

Лука Колоннелло
+39 345 8948718
Лука. [email protected]

А как насчет «компонентов макета»? Я имею в виду, что когда у вас есть много контейнеров, которые должны быть в одном компоненте, чтобы передать его маршрутизатору / навигатору, этот компонент-оболочка будет «тупым презентационным контейнером контейнеров», верно?

@ Emilios1995 У меня такая же проблема ...
У меня есть компонент страницы, который используется внутри компонента макета.
Этот компонент Layout имеет меню, верхние колонтитулы, нижний колонтитул .. Содержимое является дочерним, переданным Page в Layout ..
Меню и заголовки - это контейнер !! Таким образом, Layout потенциально является контейнером, но не связан с магазином.

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

При этом иерархия правильная, но я должен повторять меню и заголовок на каждой странице, и некоторые из них для меня одинаковы на каждой странице ..

@LucaColonnello Я не совсем понимаю проблему без кода. Могу я попросить вас создать вопрос StackOverflow с простым примером, иллюстрирующим вашу проблему? Буду рад взглянуть.

как можно скорее

Il sabato 27 февраля 2016 г., Дэн Абрамов [email protected] ha
сценарий:

@LucaColonnello https://github.com/LucaColonnello Я не совсем
разобраться в проблеме без кода. Могу я попросить вас создать
Вопрос StackOverflow с простым примером, иллюстрирующим вашу проблему? Идентификатор
будьте счастливы взглянуть.

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/reactjs/redux/issues/756#issuecomment -189672067.

Лука Колоннелло
+39 345 8948718
Лука. [email protected]

@gaearon Это не проблема, я думаю, это вопрос, который я задал здесь: http://stackoverflow.com/questions/35729025/should-the-route-handlers-use-containers-or-presentational-components? noredirect = 1 # comment59133192_35729025

Я думаю, статья Дэна о среде
https://medium.com/@dan_abramov/smart -and-dumb-components-7ca2f9a7c7d0 # .3y00gw1mq
проясняет все вопросы ...

2016-03-01 18:19 GMT + 01: 00 Эмилио Сроуго [email protected] :

@gaearon https://github.com/gaearon Это не проблема, я думаю, что это
скорее вопрос, который я сделал здесь:
http://stackoverflow.com/questions/35729025/should-the-route-handlers-use-containers-or-presentational-components?noredirect=1#comment59133192_35729025

-
Ответьте на это письмо напрямую или просмотрите его на GitHub
https://github.com/reactjs/redux/issues/756#issuecomment -190820426.

Лука Колоннелло
+39 345 8948718
Лука. [email protected]

@gaearon Интересно, если презентационный компонент может содержать внутри компоненты контейнера, как его можно использовать повторно?
К вашему сведению:

Интересно, если презентационный компонент может содержать внутри компоненты контейнера, как его можно использовать повторно?

Если все его сценарии использования включают в себя конкретный контейнер, я не вижу, что в нем нельзя использовать повторно. А если нет, заставьте его принять this.props.children и создать другие презентационные компоненты, которые передают внутри определенные контейнеры или презентационные компоненты.

@gaearon Является ли контейнерным компонентом [Корневой компонент] React-Router?

  • route.js
<Route path="/" component={Root}>
      <IndexRoute component={Main} />
      <Route path="/account/signIn" component={SignIn} />
</Route>
  • root.js
export default class Root extends React.Component {
  render() {
    return (
      <div>
        <div id="container" className="container">
          {this.props.children}
        </div>
      </div>
    );
  }

Спасибо.

@gaearon выше вы сказали, что предпочитаете соединять компоненты, чтобы получить доступ к диспетчеризации (в отличие от передачи их от родительских компонентов).

Если вы подключите эти компоненты, и у них также есть реквизиты, которые _ могут_ быть сопоставлены с редукторами, которые в настоящее время передаются родителем, вы бы реорганизовали их, чтобы они исходили из connect ? Или используйте ownProps чтобы они передавались родителем.

Есть ли разница в функциональности / производительности между двумя вариантами?

У меня нет большого опыта работы с огромными проектами redux, но я много исследовал и много думал об оптимизации структур файлов response / redux. Дайте мне знать, есть ли в этом смысл или нет:

src/
  components/
    header/ 
      navigation.js # nav menu list
      index.js # Header component
  modules/
    header/
      actions.js # header actions (sticky scroll, collapse mobile menu, etc...)
      reducer.js # header actions reducer (export to modules index)
      index.js # HeaderContainer (connect to Header component)
    index.js # combineReducers and export default configureStore (and middleware)

другая концепция:

src/
  components/
    navigation.js
    logo.js
    link.js
    list.js
    item.js
  modules/
    header/
      actions.js # header actions 
      wrapper.js # header class (smart) component - to wrap header with functionality (was previously classified as container)
      container.js # header functional (dumb) component - to contain universal components
      index.js # header actions reducer - to export into modules rootReducer 

Или лучше хранить компоненты, контейнеры и модули redux отдельно (даже если у них одно и то же имя)? Спасибо за любой вклад.

Я работал с проектами response-redux на уровне предприятия, и по своему опыту могу сказать одну вещь, что только от вас зависит, как определить архитектуру вашего проекта. Я не слежу ни за какими архитектурными вариациями на основе контейнеров / компонентов, потому что они непрактичны в том смысле, что цель React состоит в создании пользовательского интерфейса на основе повторно используемых компонентов.

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

image
image
image

Для меня контейнер и смарт-компонент - это одно и то же. Простое определение:
Контейнер / смарт-компонент - это тот, который содержит разметку JSX + обработчики событий + вызовы API + соединение redux / MSTP / MSDP.
Тупой компонент - ИСТИННО презентационный, функциональный компонент.

Была ли эта страница полезной?
0 / 5 - 0 рейтинги

Смежные вопросы

CellOcean picture CellOcean  ·  3Комментарии

rui-ktei picture rui-ktei  ·  3Комментарии

ramakay picture ramakay  ·  3Комментарии

mickeyreiss-visor picture mickeyreiss-visor  ·  3Комментарии

captbaritone picture captbaritone  ·  3Комментарии