随着我进一步将redux+react实现到一个相当复杂的应用程序中,该应用程序依赖于加载单个页面的许多API请求,我很难决定在页面的根位置设置一个容器组件是否更好,该组件处理所有异步内容并将道具传递给哑组件,v.s.具有多个容器组件,这些组件只关心它们所需的数据,以及获取它们所需要的数据。我在这两种模式之间来回切换,发现它们都有优点/缺点:
如果我在顶部放一个容器组件:
-
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多个组件时,一切都变得非常复杂,我发现这些顶级容器组件的复杂性太高,我不喜欢,因为它们中的大多数(在我正在使用的应用程序中(都依赖于3个以上API请求的响应。
然后我决定尝试多个容器:
- pro:完全无需转发道具。在某些情况下这样做仍然有意义,但它要灵活得多
- pro:重构更容易。我感到惊讶的是,我可以在不破坏任何东西的情况下显著地移动和重构组件,而在其他模式中,事情破坏了很多
- pro:每个容器组件的复杂性要低得多。我的
mapStateToProps
和mapDispatchToProps
更特定于它所在组件的用途 - con:任何依赖异步内容的组件都需要自己处理
isFetching
状态。这增加了在单个容器组件中处理的模式中不需要的复杂性
因此,主要的困境是,如果我使用一个容器,我会在根容器和叶组件之间的组件中获得这种不必要的复杂性。如果我使用多个容器,叶组件会变得更加复杂,并且最终会出现需要担心isFetching
的按钮,即使按钮不应该担心这一点。
我想知道是否有人找到了避免这两种缺点的方法,如果是,你遵循什么"经验法则"来避免这种情况?
谢谢!
我一直认为,容器位于逻辑组件组的最顶端,而不是根/应用程序组件。
因此,如果我们有一个简单的搜索应用程序来显示结果,并假设组件高度是这样的
<Root> <- setup the app
<App>
<Search/> <- search input
<Results/> <- results table
</App>
</Root>
我会使Search
和Results
的redux感知容器。因为react组件被认为是可组合的。您可能有其他组件或页面需要显示Results
或Search
。如果您将数据获取和存储意识委托给根或应用程序组件,则会使组件变得相互依赖/应用程序。当你必须实施更改时,这会使事情变得更加困难,现在你必须更改所有使用它们的地方。
如果组件之间确实有紧密耦合的逻辑,则可能会出现这种情况。即便如此,我还是要说,你应该创建一个容器来包装你紧密耦合的组件,因为如果没有彼此,它们就无法实际使用。
Redux的作者Dan Abramov建议您在需要容器组件时使用它们。也就是说,一旦有太多的道具在组件之间来回布线,就应该使用容器了。
他称之为"正在进行的重构过程"。
请参阅本文:https://medium.com/@dan_abramov/smart-and-dumb-components-7ca2f9a7c7d0
我甚至不会考虑使用单个容器的方法。它几乎完全否定了redux的所有优点。如果您的所有状态都在一个位置,并且所有回调都位于一个位置(根组件(,那么就不需要有状态管理系统。
不过,我认为还有一线路要走。我正在制作一个应用程序,我已经使用了大约5周(兼职(,现在已经有3000行了。它有3个层次的视图嵌套,一个我自己实现的路由机制,以及需要修改状态的10多个层次嵌套的组件。我基本上为每个大屏幕都有一个redux容器,它的工作效果非常好。
然而,如果我点击我的"客户端"视图,我会得到一个客户端列表,这很好,因为我的客户端视图位于redux容器中,并获得作为道具传递的客户端列表。然而,当我点击一个客户端时,我真的很犹豫是否为单个客户端的配置文件做另一个redux容器,因为这只是传递道具的一个附加级别。看起来,根据应用程序的范围,您可能希望通过redux容器传递最多1-2个级别的道具,如果超过这个级别,那么只需创建另一个redux容器。再说一遍,如果它是一个更复杂的应用程序,那么有时使用redux容器,有时不使用它们的混合可能会使可维护性更差。简言之,我的观点是尽可能减少redux容器,但绝对不能以牺牲复杂的道具链为代价,因为这是使用redux的要点。
所以我发布这个问题已经两年多了我一直在与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
,但当我这样做时,通常是因为:
例如,我需要防止第二次单击提交按钮,这会进行API调用,在这种情况下,可能应该调用道具
disableSubmit
,而不是isFetching
或当有东西在等待异步响应时,我想显式地显示一个加载程序。
现在,你可能会想,"在上面的todos示例中,当项目被提取时,你不想显示一个加载器吗?"但在实践中,实际上我不会。
原因是在上面的例子中,假设您正在轮询新的todo,或者当您添加todo时,您"重新蚀刻"了todo。在第一个例子中,每次发生这种情况时,todo都会消失,并经常被替换为"Loading…"。
然而,在与isFetching
无关的第二个示例中,新项目被简单地添加/移除。在我看来,这是更好的用户体验。
事实上,在发布这篇文章之前,我查看了我编写的交换接口的所有UI代码,该接口非常复杂,没有发现任何一个必须将isFetching
连接到我编写的容器组件的实例。
您不必在同一位置调度和加载您的状态。
换句话说,您的按钮可以调度异步请求,而另一个组件可以检查您是否正在加载。
例如:
// < 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
});