【问题标题】:Passing store state as props, or each component accessing global stores?将商店状态作为道具传递,还是每个组件都访问全局商店?
【发布时间】:2014-12-21 05:41:06
【问题描述】:

我对“渲染整个应用程序”和“将状态传递给子组件”这两种说法感到有些困惑。

示例 1:

我有一个带有AppComponentTodosListComponent 的待办事项应用程序。 AppComponent 从存储中获取待办事项数组并将其作为属性传递给TodosListComponent

示例 2:

我有一个包含很多状态的庞大应用程序。我有 50 个组件来构建我的应用程序。我是否想将商店中的所有状态从 AppComponent 向下传递到所有 50 个组件?

所以我想知道,约定是什么?让单个组件直接听他们关心的商店对我来说更有意义。好处是只有个别组件会重新渲染,但为什么会出现“整个应用程序在状态变化时重新渲染”的概念呢?

各有什么优缺点?常见的约定是什么?

【问题讨论】:

  • 因为当你发现一个 bug 并且你去调查它时,你所有的状态都在一个地方,让一切都超级容易追踪和修复。
  • 所以你会让一个顶级组件从存储中获取所有状态并通过属性将其传递给所有其他 50 个嵌套组件?
  • 不一定。它高度依赖于其他 50 个嵌套组件是什么。用你最好的判断。您可以阅读本文开始:facebook.github.io/react/docs/…
  • 嗯,是的,虽然这不是将状态放入存储或组件的问题。我认为这很清楚。这是我不想将 100 个状态下推到顶部组件中,并且在每个嵌套级别的组件上将状态重定向到较低级别的组件。对我来说,这感觉非常混乱。但这是我可以从“重新渲染整个应用程序”中得出的唯一结论。让组件直接从存储中获取他们想要的状态感觉好多了……嗯嗯嗯
  • 显然是高度基于意见的,因为它实际上是在征求意见。

标签: javascript reactjs flux


【解决方案1】:

有几种方法可以解决这个问题。我认为它们都是有效的,并且有自己的取舍。

获取所有状态并将其部分传递给孩子

这是您特别询问的技术。使用此方法,您将拥有一些可用于顶级组件的函数或方法,将存储中的所有数据转换为“状态大袋”,然后您将有选择地将这些数据片段传递给子组件。如果这些组件有自己的子组件,它们会根据需要传递它。

这种方法的好处是它使事情通常易于调试。如果您必须更改从存储中检索状态的方式,您只需在顶级组件中更改它 - 只要它以相同的名称传递,其他组件将“正常工作”。 "如果某条数据有误,您只需查看一处即可找出原因。

这种技术的缺点是我称之为“道具爆炸”——你最终可能会传递很多个属性。我在一个中型的flux应用中使用这个方法,顶层应用组件的一个sn-p看起来是这样的:

<section id="col-left">
  <Filters loading={this.state.loading}
            events={this.state.events}
            playbackRate={this.state.videoPlayback.playbackRate}
            autoPlayAudio={this.state.audioPlayback.autoPlay}
            role={this.state.role} />
</section>

<section id="col-center" className={leftPaneActive ? "" : "inactive"}>
  <SessionVideo videoUuid={this.state.session.recording_uuid}
                lowQualityVideo={this.state.session.low_quality_video_exists}
                playbackRate={this.state.videoPlayback.playbackRate} />
  <section id="transcript">
    <Transcript loading={this.state.loading}
                events={this.state.events}
                currentEvents={this.state.currentEvents}
                selection={this.state.selection}
                users={this.state.session.enrolled_users}
                confirmedHcs={this.state.ui.confirmedHcs}

                currentTime={this.state.videoPlayback.position}
                playing={this.state.videoPlayback.playing} />
  </section>
</section>

特别是,在顶层组件和一些最终的子组件之间可能有很多组件,它们除了传递数据之外什么都不做,将这些组件更紧密地耦合到它们在层次结构中的位置。

总体而言,我喜欢这种技术提供的可调试性,但随着应用程序变得越来越大和越来越复杂,我发现仅使用单个顶级组件来实现这一点并不理想。

获取所有状态并将其作为一个对象传递

Facebook 的一位开发人员提到了这种技术。在这里,您将获得一大袋状态,就像上面一样,但您将传递整个事物(或它的整个子部分)而不是单个属性。通过在子组件中使用React.PropTypes.shape,您可以确保传递正确的属性。

好处是你传递的属性更少;上面的例子可能看起来更像这样:

<section id="col-left">
  <Filters state={this.state} />
</section>

<section id="col-center" className={leftPaneActive ? "" : "inactive"}>
  <SessionVideo session={this.state.session}
                playback={this.state.videoPlayback} />
  <section id="transcript">
    <Transcript state={this.state} />
  </section>
</section>

缺点是处理状态形状的变化变得有点困难;您不仅要更改顶级组件,还必须跟踪使用该数据的任何地方,并更改组件访问属性的方式。此外,shouldComponentUpdate 的实现可能会变得有些棘手。

允许组件获得自己的状态

另一方面,您可以授予特定于应用程序(即不可重用)的子组件来访问存储并根据存储更改事件建立其自己的状态.像这样构建自己的状态的组件有时称为“控制器视图”,或者现在更常见的是“容器组件”。

当然,好处是您根本不必处理传递属性(除了更改处理程序和属性以获得更多可重用组件)。

不过,缺点是您的组件与商店的耦合度更高 - 更改商店或它们提供的数据(或它们提供数据的接口)可能会迫使您重新访问更多的代码组件。

此外,正如 cmets 中所述,这可能会使服务器渲染更加困难。如果您只使用属性(尤其是仅在顶层),您可以更轻松地将它们传输到客户端并使用相同的属性重新初始化 React。通过允许商店确定自己的数据,您需要以某种方式将该数据注入到商店中以允许组件获取该数据。

一种常见的方法,也是我现在通常使用的一种方法,是让应用程序中的每个组件仅依赖于全局应用程序状态的 props,然后决定 (1) 通过以下方式将它们直接连接到 Flux 是否更有意义将它们包装在容器中,或者 (2) 允许从某个父容器传递道具。


您可以使用一些抽象来使其中一些技术更可行。例如,一位 Facebook 开发人员在 a comment on Hacker News 中这样说:

现在您的所有数据都在存储中,但是您如何将其放入需要它的特定组件中?我们从大型顶级组件开始,这些组件提取其子级所需的所有数据,并通过 props 向下传递。这会导致中间组件中出现大量繁琐且不相关的代码。在大多数情况下,我们决定的是组件自己声明和获取他们需要的数据,除了一些更小、更通用的组件。由于我们的大部分数据都是异步获取和缓存的,因此我们创建了 mixin,可以轻松声明组件需要哪些数据,并将获取和侦听更新挂钩到生命周期方法(componentWillMount 等)。

【讨论】:

  • 感谢布兰登的精彩回答。这完美地指出了我的困惑!我认为这里也值得一提的是服务器端渲染。如果您从“顶级组件”传递状态,那么使用服务器端渲染会更容易,因为您从“一个地方”传递状态。这里的最后一个解决方案可能需要您覆盖存储或使它们在服务器上的行为不同。
  • 感谢您的精彩解释。考虑“混合”,即让顶级组件通过 props 接收初始状态,它将创建并保持对 store 的引用,并且 stores 将通过 props 以一种 DI/ 方式传递给组件存储库样式。顶级组件订阅存储更改,调用forceUpdate
【解决方案2】:

来自 Facebook explained 的 Jason Bonta 在他的 React.js Conf 2015 演讲中谈到了“容器”的概念。

总而言之:容器只是包装其他组件的简单组件,并负责处理任何与数据相关的问题,例如与存储通信,而底层组件仅关注视图(标记/样式/等)并且不不关心数据来自哪里。

这使得组件

  • 高度可重用,因为当数据需要来自不同位置时,它可以用不同的容器包装,

  • 不包含无关状态,因此更容易实现和优化shouldComponentUpdate,并且

  • 为此使用组合而不是混合与 what is likely the future of React 与 ES6 保持一致,后者没有惯用的混合。

更新 2019 年 3 月:查看 React Hooks。使用钩子,您可以实现与上述相同的目标,但可以将与数据相关的问题(例如与商店交谈)抽象为可应用于多个组件的可重用代码块。 Dan Abramov 的 ReactConf 演讲 React Today and Tomorrow and 90% Cleaner React With Hooks 很好地解释了钩子,以及它们与 mixin 和过去的组合方法有何不同。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2015-07-04
    • 2021-02-06
    • 2020-08-21
    • 1970-01-01
    • 2020-09-01
    • 1970-01-01
    • 1970-01-01
    • 2020-05-22
    相关资源
    最近更新 更多