React Redux:更多容器vs.更少的容器

时间:2016-09-07 19:30:26

标签: reactjs redux react-redux

随着我进一步实施redux +反应到一个相当复杂的应用程序,这取决于许多API请求加载单个页面,我无法决定是否有一个单一的容器组件更好在页面的根部处理所有异步的东西,并将道具传递给哑组件,vs拥有多个容器组件,只关注它们所需的数据,以及获取所需的数据。我在这两种模式之间来回走动,发现它们各有利弊:

如果我将一个容器组件放在顶部:

  • 专业人士:所有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多个组件,它们都感觉非常纠结,我发现这些顶级容器组件的复杂性太高,不符合我的喜好,看到它们中的大多数(在我正在处理的应用程序依赖于来自3个以上API请求的响应。

然后我决定尝试多个容器:

  • 亲:完全不需要转发道具。在某些情况下这样做仍然有意义,但它更灵活。
  • 亲:更容易重构的方式。我惊讶于如何在没有任何破坏的情况下显着移动和重构组件,而在其他模式中,事情已经破坏了很多。
  • pro:每个容器组件的复杂程度要低得多。我的mapStateToPropsmapDispatchToProps更具体针对其所在组件的目的。
  • con:任何依赖异步内容的组件都需要自己处理isFetching状态。这增加了在单个容器组件中处理它的模式中不必要的复杂性。

所以主要的困境是,如果我使用一个容器,我会在根容器和叶子组件之间的组件中获得这种不必要的复杂性。如果我使用多个容器,我会在叶子组件中获得更多复杂性,并最终得到需要担心isFetching的按钮,即使按钮不应该关注它。

我想知道是否有人找到了避免利弊的方法,如果有的话,那么&#34;经验法则是什么?你遵循以避免这个?

谢谢!

5 个答案:

答案 0 :(得分:2)

我一直认为,将容器放在除root / app组件之外的逻辑组件组的最顶层组件中。

因此,如果我们有一个显示结果的简单搜索应用程序,并假设组件heiarchy就是这样

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

我会让SearchResults感知redux的容器。因为反应组分被认为是可组合的。您可能有其他需要展示ResultsSearch的组件或页面。如果将数据获取和存储感知委托给根或应用程序组件,则会使组件变得依赖于彼此/应用程序。当您必须实施更改时,这会使线路变得更难,现在您必须更改使用它们的所有位置。

如果您在组件之间确实存在非常紧密耦合的逻辑,则可能存在例外情况。即使这样,我会说你应该创建一个包装紧密耦合组件的容器,因为它们不能在没有彼此的情况下实际使用。

答案 1 :(得分:2)

Redux作者Dan Abramov建议您在需要时使用容器组件。也就是说,一旦你有太多的道具在组件之间上下布线,就可以使用容器了。

他称之为“正在进行的重构过程”。

请参阅此文章:https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0

答案 2 :(得分:1)

我甚至不会考虑使用单一容器方法。它几乎完全否定了redux的所有优点。如果您的所有州都在一个地方并且所有回调都在一个地方(根组件),则无需任何状态管理系统。

但是,我觉得走路的路线很细。我正在制作一个应用程序,我已经在那里工作了大约5周(兼职),现在它已达到3000行。它有3个级别的视图嵌套,我自己实现的路由机制,以及需要修改状态的10个以上嵌套级别的组件。我基本上每个大屏幕都有一个redux容器,它的工作非常棒。

但是,如果我点击我的&#34;客户&#34;查看,我得到一个客户列表,这很好,因为我的客户端视图在redux容器内,并获取作为道具传递的客户列表。但是,当我点击一个客户端时,我真的很犹豫为个人客户端的配置文件做另一个redux容器,因为它只有一个额外级别的传递道具。似乎根据应用程序的范围,您可能希望将props返回到redux容器之前的1-2级,如果它不止于此,那么只需创建另一个redux容器。然后,如果它是一个更复杂的应用程序,那么有时使用redux容器和其他一些不使用它们的混合对于可维护性来说可能更糟糕。简而言之,我的观点是尽可能地尽量减少redux容器,但绝对不能以复杂的支撑链为代价,因为这是使用redux开始的主要原因。

答案 3 :(得分:1)

从我发布这个问题到现在已经超过2年了 我一直在与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而不是{{1} }或

  2. 当某些内容正在等待异步响应时,我想显式显示加载程序。

现在,您可能会想,“当您在上面的todos示例中提取项目时,是否不想显示装载程序?”但实际上,我不会。

这样做的原因是,在上面的示例中,假设您正在轮询新的待办事项,或者在添加待办事项时“重新提取”了待办事项。在第一个示例中将发生的事情是,每次发生此事,待办事项就会消失,并被“ Loading ...”频繁替换。

但是,在与isFetching不相关的第二个示例中,新项目只是简单地附加/删除。我认为这是更好的UX。

实际上,在发布此内容之前,我经历了我编写的交换接口的所有UI代码,该接口非常复杂,没有找到必须将isFetching连接到我编写的容器组件的单个实例

答案 4 :(得分:1)

您不必在同一位置分派和加载状态。

换句话说,您的按钮可以调度异步请求,而另一个组件可以检查您是否正在加载。

例如:

// < SomeButtonThatFetchesData.js>

const mapDispatchToProps = (dispatch) => ({
  onLoad: (payload) => 
    dispatch({ type: DATA_LOADED, payload })
});

您将需要一些中间件来处理加载状态。当您传递异步有效负载时,它需要更新isFetching

例如:

const promiseMiddleware = store => next => action => {
  if (isPromise(action.payload)) {
    store.dispatch({ type: ASYNC_START, subtype: action.type });

然后,您可以在任何需要的地方使用它:

// <MyContainer.js>

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

并将数据加载到内部嵌套组件中

// <SomethingThatDependsOnData.js>

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