Redux: 容器还是组件?

创建于 2015-09-19  ·  46评论  ·  资料来源: reduxjs/redux

在示例中,始终有一个名为“容器”的文件夹和一个名为“组件”的文件夹。 这种分离背后的想法是什么?

是“容器”智能组件,还是路由组件或其他组件? “组件”下的组件是否应该总是愚蠢的?

docs question

最有用的评论

我称components封装的React组件仅由props驱动,不与Redux对话。 与“哑部件”相同。 无论您的路由器,数据提取库等如何,它们都应保持不变。

我将知道Redux,Router等的containers React组件称为。它们与应用程序的耦合程度更高。 与“智能组件”相同。

所有46条评论

对于我来说, container是路由的处理程序,它还会拉取该路由的redux状态。 然后我把自己的状态作为支柱。

例如,

container / 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指出的,使每个路由处理程序/组件成为智能/容器组件也是很常见的。 如果您使用CombineReducers,通常会发现自己沿着相同的路线破坏状态,路线和容器。 因此,它们经常联系在一起。

@ronag我不认为您需要制作容器页面。 为什么您不能将路由处理程序和redux连接器放在1个位置上? 非常方便。 把事情简单化。

在我的应用程序中,我只有3条路线/页面。 另一方面,我有很多独立的面板/模块。 制作一个巨大的connect是不可行的,我至少需要按照面板进行拆分。

如果我从状态到道具进行所有映射,并在单个文件中获取所有数据,则将无法维护。 特别是如果我在同一位置提取所有数据。

如果您有3条路线,那么您需要3条路线处理程序。 例如, A.jsxB.jsxC.jsx /containers

在每个容器中,您拉取部分(不是全部!)redux状态,并绑定动作。 然后,将其传递给类似于我的示例的组件。 这是非常容易维护的,因为我(和我的团队)知道—连接到redux并且动作总是在containers绑定,它永远不会在components (这在我的示例中很小并且通常是无状态的)。

我想我理解你的建议。 但是,正如我所说,如果我将所有数据都放在这里,那三个文件将变得非常复杂。 由于我们的原因,基本上是“登录,应用程序,注销”,其中应用程序将包含几乎所有内容。

也许我太偏向于Relay ,其中每个组件都有一个容器来描述要获取的数据以及其外观。

我将尝试您的建议,看看最终结果。

我称components封装的React组件仅由props驱动,不与Redux对话。 与“哑部件”相同。 无论您的路由器,数据提取库等如何,它们都应保持不变。

我将知道Redux,Router等的containers React组件称为。它们与应用程序的耦合程度更高。 与“智能组件”相同。

@gaearon :完美。 这就澄清了例子。 谢谢你。

@gaearon因此,“哑组件”是无状态组件,可以使用从React 0.14开始的更简单语法编写,例如:

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

我对么?

@soulmachine是的。

不必要。 语法不是重点。

“哑巴”组件又称“呈现”组件,它们是通过prop接收所有数据的组件,不了解Redux,仅指定外观,而不指定行为。

@theaqua @gaearon谢谢!

尽管“容器”一词在react / redux术语中已经很流行,但我们还是决定在我正在研究的项目中将它们称为“连接器”,以避免与布局/图形容器混淆。

顺便说一句,如何调用它们在前面这里曾讨论过rackt / react-redux#170。 我将所有内容都保存在components文件夹中,并将演示文稿更深入地保存在components/presentational components文件夹中,我认为这很好,因为除了容器之外,应用程序的其他部分不太可能需要它们。

@gaearon会将dispatch视为“哑”或“智能”的组件吗? 让叶或中间组件分派操作而不是将事件冒泡回到顶部组件进行分派,这特别有用。

是的,虽然我更喜欢connect()这样的组件,所以偶尔会有用,所以动作创建者被注入为道具。 你怎么称呼它并不重要:-)

当您构建组件模块时该怎么办? 例如,假设我为导航菜单<NavMenu>有一个单独的节点模块,我希望人们这样做:

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

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

所以我只将其命名为“ NavMenuContainer”吗? 在我看来,这很奇怪。 我应该改为命名组件NavMenuComponent吗? 两者都对我来说很奇怪。 在这种情况下,该组件仅调用connect以从该状态获取1字段。 像这样直接插入连接真的真的很糟糕吗?

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

好奇地听到您的想法@gaearon关于何时(如果有的话)只是内联connect调用是“好的” ...

怎么称呼它都没关系。 为什么不称之为NavMenu呢?

我不了解有关内联的问题。
如果您提出了两种比较的方法,将会有所帮助。

好的,例如。
选项1(2个单独的文件,1个用于容器,1个用于组件)

容器/导航菜单

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

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

选项2(1个包含两个文件的单个文件):
组件/导航菜单

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>;
  }
});

内联的意思是只有一个文件(而不是2个文件),它既是容器又是组件,因为从状态只需要currentPath的一点点。 这只是应该避免的事情,还是在像这样的简单情况下这样做是合理的?

当然,在简单的情况下这样做是合理的。

嗯不错。 您什么时候画线并说“好吧,我会将其移动到2个单独的文件中”?

当组件开始将数据关注点(如何检索/计算数据,如何调度动作)与表示(外观)混合在一起时。 当难以在不同上下文中进行测试或重用时。

@benmonro还有一个想法。 如果要构建组件模块,则有时需要将这些组件连接到应用程序状态树的不同部分,或分别使用各种状态测试它们的表示。 在这种情况下,在此组件模块中包含connect将限制此功能。

谢谢@gaearon

@sompylasar非常好一点! 谢谢

嗯,经过我的代码以将其重构以将其拆分后,我现在有一个后续问题。 假设我还有一个<NavMenuItem>容器。 <NavMenu>组件需要引用<NavMenuItem />作为子元素。 我应该只在NavMenu组件中执行import NavMenuItem from '../containers/NavMenuItem'吗? 这如何影响@sompylasar的可测试性?

我会将NavMenuItem做成一个纯粹的演示组件,并将所有需要的数据通过NavMenu通过props传递给它。 这将允许对其进行单独测试。 因此,您将有两个表示形式的(NavMenu,NavMenuItem)和一个已连接的(connect(...)(NavMenuItem))组件。

在此讨论中我缺少的一件事:将它们放在一个文件中时,不能使用浅层渲染进行测试。 我见过人们公开展示性组件和容器组件来解决此问题,然后分别进行测试,因此在这种情况下,我宁愿有两个文件来明确表明这是两件事。 这也突出了这里关注点的分离以及表示组件是独立且可重用的事实。

FWIW我更新了Presentational and Container Components文章以反映我当前的想法。

我们不再鼓励在更新的文档中创建容器组件。
http://redux.js.org/docs/basics/UsageWithReact.html

在Redux文档的实际示例中, @ gaearon似乎组件可以将容器作为子容器。
我错了吗? 这如何影响在其内部呈现智能组件的哑巴组件的可测试性?
但是我找不到让组件成为层次结构中最新组件的方法...

我有一个呈现简单数据列表组件(Dumb)的应用程序。
里面的每件商品都必须连接到商店,因此这是一个明智的选择。

根据文档可以,但是会带来一些问题吗?

感谢!

这如何影响在其内部呈现智能组件的哑巴组件的可测试性?

这确实使测试更难设置(您还需要初始化存储)。 如果不方便,请提取更多接受children演示性组件,以便您可以在其中传递容器组件。 通常,划分取决于您,您需要评估权衡(易于编写,重构,测试等),并选择如何为您自己分离组件。

好的,所以没有正确的方法。 我们必须逐案评估。许多
谢谢!!

Illunedì2016年2月8日,Dan Abramov [email protected] ha
字幕:

这如何影响在其内部渲染的哑巴组件的可测试性
一个聪明的人?

这确实使测试更难设置(您需要初始化
商店)。 如果这不方便,请提取更多
可以接受孩子的演示组件,因此您可以传递容器
内部组件。 通常,划分取决于您,而您需要
评估权衡(易于编写,重构,测试等),并
选择如何自行分离组件。

-
直接回复此电子邮件或在GitHub上查看
https://github.com/rackt/redux/issues/756#issuecomment -181143304。

卢卡·科洛内洛(Luca Colonnello)
+39 345 8948718
卢卡 [email protected]

那“布局组件”呢? 我的意思是,当您有多个容器需要放在一个组件中才能将其传递到路由器/导航器时,此包装组件将成为“笨拙的容器的外观容器”,对吗?

@ Emilios1995我有同样的问题...
我有一个在布局组件内使用的Page组件。
此Layout组件具有菜单,页眉和页脚。.内容是Page传递给Layout。的子级。
菜单和标题都是容器!! 因此Layout可能是一个容器,但未连接到商店。

但是,如果我尝试传递菜单和标题的布局,则会有一个页面(容器)呈现布局(组件)并将菜单和标题(容器)传递给它。

这样做的层次结构是正确的,但是我必须在每页中重复菜单和标题,对我来说,每页中的菜单位都是相同的。

@LucaColonnello如果没有代码,我不太了解这个问题。 我是否可以要求您创建一个StackOverflow问题,并提供一个简单的示例来说明您的问题? 我很乐意看看。

尽快

Il sabato 2016年2月27日,Dan Abramov [email protected] ha
字幕:

@LucaColonnello https://github.com/LucaColonnello我不太清楚
无需代码即可了解问题。 我可以请您创建一个
有一个简单的示例来说明您的问题的StackOverflow问题? ID
很高兴看看。

-
直接回复此电子邮件或在GitHub上查看
https://github.com/reactjs/redux/issues/756#issuecomment -189672067。

卢卡·科洛内洛(Luca Colonnello)
+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 Emilio Srougo [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。

卢卡·科洛内洛(Luca Colonnello)
+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表示,您更喜欢连接组件以便获得对分发的访问权限(而不是将其从父组件向下传递)。

如果连接这些组件,并且它们也具有从父级当前正在传递的reducer _could_映射的props,您是否可以将它们重构为connect ? 或使用ownProps来保持它们通过父母。

两种选择之间在功能/性能上有区别吗?

我没有处理大型Redux项目的丰富经验,但是我一直在研究和思考很多有关优化react / 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模块分开(即使它们共享相同的名称)会更好吗? 感谢您的任何投入。

我曾经在企业级React-redux项目中工作过,根据我的经验,我可以说一件事,那就是它完全取决于您如何定义项目的体系结构。 我不遵循任何基于容器/组件的体系结构变体,因为就React的目的是使基于UI的组件可重用的意义而言,它们是不切实际的。

因此,我想出了一种基于模块将整个项目分组的简单方法,到目前为止,在可伸缩性,可维护性和代码可读性方面,它一直非常有效。

image
image
image

对我来说,容器和智能组件是完全相同的。 简单的定义是:
容器/智能组件是一个包含JSX标记+事件处理程序+ api调用+ redux的connect / MSTP / MSDP的组件。
愚蠢的组件是纯粹的呈现功能组件。

此页面是否有帮助?
0 / 5 - 0 等级

相关问题

benoneal picture benoneal  ·  3评论

caojinli picture caojinli  ·  3评论

timdorr picture timdorr  ·  3评论

vraa picture vraa  ·  3评论

mickeyreiss-visor picture mickeyreiss-visor  ·  3评论