ReactJS - 提升状态与保持本地状态
Posted
技术标签:
【中文标题】ReactJS - 提升状态与保持本地状态【英文标题】:ReactJS - Lifting state up vs keeping a local state 【发布时间】:2018-03-17 14:53:18 【问题描述】:在我的公司,我们正在将 Web 应用程序的前端迁移到 ReactJS。 我们正在使用 create-react-app(更新到 v16),没有 Redux。 现在我被困在一个可以通过下图简化结构的页面上:
三个组件(SearchableList、SelectableList 和 Map)显示的数据在 MainContainer 的componentDidMount()
方法中使用相同的后端请求检索。然后这个请求的结果被存储在 MainContainer 的状态中,并且具有或多或少这样的结构:
state.allData =
left:
data: [ ... ]
,
right:
data: [ ... ],
pins: [ ... ]
LeftContainer 从 MainContainer 接收作为 prop state.allData.left
并将 props.left.data
传递给 SearchableList,再次作为 prop。
RightContainer 从 MainContainer 接收作为 prop state.allData.right
并将 props.right.data
传递给 SelectableList 和 props.right.pins
传递给 Map。
SelectableList 显示一个复选框以允许对其项目进行操作。每当在 SelectableList 组件的某个项目上发生操作时,它可能会对 Map 引脚产生副作用。
我决定在 RightContainer 的状态中存储一个列表,该列表保留 SelectableList 显示的所有项目的 id;此列表作为道具传递给 SelectableList 和 Map。然后我将一个回调传递给 SelectableList,每当进行选择时,都会更新 RightContainer 中的 id 列表;新的 props 到达 SelectableList 和 Map,因此在两个组件中都调用了 render()
。
它工作正常,有助于将可能发生在 SelectableList 和 Map 上的所有事情保存在 RightContainer 中,但我想问这对于 lifting-state-up 和 single- 是否正确真相的来源 概念。
作为可行的替代方案,我想为 MainContainer 中 state.right.data
中的每个项目添加一个 _selected
属性,并将选择回调向下传递三个级别到 SelectableList,处理 MainContainer 中的所有可能操作。但是一旦选择事件发生,这最终将强制加载 LeftContainer 和 RightContainer,从而需要实现 shouldComponentUpdate()
之类的逻辑来避免无用的 render()
,尤其是在 LeftContainer 中。
从架构和性能的角度来看,哪个/可能是优化此页面的最佳解决方案?
下面是我的组件的摘录,以帮助您了解情况。
MainContainer.js
class MainContainer extends React.Component
constructor(props)
super(props);
this.state =
allData:
;
componentDidMount()
fetch( ... )
.then((res) =>
this.setState(
allData: res
);
);
render()
return (
<div className="main-container">
<LeftContainer left=state.allData.left />
<RightContainer right=state.allData.right />
</div>
);
export default MainContainer;
RightContainer.js
class RightContainer extends React.Component
constructor(props)
super(props);
this.state =
selectedItems: [ ... ]
;
onDataSelection(e)
const itemId = e.target.id;
// ... handle itemId and selectedItems ...
render()
return (
<div className="main-container">
<SelectableList
data=props.right.data
onDataSelection=e => this.onDataSelection(e)
selectedItems=this.state.selectedItems
/>
<Map
pins=props.right.pins
selectedItems=this.state.selectedItems
/>
</div>
);
export default RightContainer;
提前致谢!
【问题讨论】:
如果RightContainer
组件之外的其他组件不应该知道这些更改,那么我认为没有必要在更高级别的组件中管理此类数据。作为一般规则,当我不使用redux
时,我会在所有需要特定数据的组件的父级管理数据状态。
【参考方案1】:
通过使用 Redux,您可以避免此类回调并将整个状态保持在一个存储中 - 所以让您的父组件连接组件 - 并使左右组件成为哑组件 - 只需将您从父组件获得的道具传递给子组件- 在这种情况下您不必担心回调。
【讨论】:
我想避免使用 Redux,我知道它可以解决很多与状态管理相关的问题,但是正如 D.Abramov(Redux 的合著者)所说的 here首先学习以 React 的方式思考,因为 Redux 引入了新的复杂度。在同一篇文章中它还说:最后,不要忘记您可以在不使用 Redux 的情况下应用来自 Redux 的想法。例如,考虑一个具有本地状态的 React 组件。 我认为这是正确的想法——只要它仍然相当容易遵循,使用回调就没有错,它似乎就在这里。 Redux 非常适合跨越大型层次结构的非常复杂的状态;对于像这样简单的事情,它可能会让人觉得有很多不必要的仪式。【参考方案2】:如 React 文档所述
通常,多个组件需要反映相同的变化数据。我们 建议将共享状态提升到最接近的公共状态 祖先。
任何变化的数据都应该有一个单一的“事实来源” 在 React 应用程序中。通常,首先将状态添加到 需要它进行渲染的组件。那么,如果其他组件也 需要它,你可以把它举到他们最近的共同祖先那里。反而 尝试在不同组件之间同步状态时,您应该 依赖自上而下的数据流。
提升状态涉及编写比双向代码更多的“样板”代码 具有约束力的方法,但作为一个好处,它需要更少的工作来查找和 隔离错误。由于任何状态都“存在”在某个组件中,并且 单独的组件可以改变它,错误的表面积很大 减少。此外,您可以实现任何自定义逻辑来拒绝或 转换用户输入。
所以本质上你需要将那些被 Siblings 组件用完的状态提升到树上。因此,您首先实现将selectedItems
存储为RightContainer
中的状态是完全合理的,并且是一种好方法,因为父级不需要知道并且此data
由两个@987654325 共享RightContainer
的 @ 组件和这两个现在有一个单一的事实来源。
根据你的问题:
作为可行的替代方案,我想添加一个 _selected 属性到
state.right.data
中的每个项目MainContainer
并通过选择 回调三个级别到SelectableList
,处理所有的MainContainer
中的可能操作
我不同意这是比第一个更好的方法,因为您 MainContainer
不需要知道 selectedItems
或处理任何更新。 MainContainer
对这些州没有做任何事情,只是将其传递下去。
考虑到optimise on performance
,您自己谈论实现shouldComponentUpdate
,但您可以通过扩展React.PureComponent
创建组件来避免这种情况,shouldComponentUpdate
实质上实现了shouldComponentUpdate
,shallow
与state
的比较和props
。
根据文档:
如果您的 React 组件的
render()
函数呈现相同的结果 给定相同的道具和状态,您可以使用React.PureComponen
t 在某些情况下性能提升。
但是,如果多个深度嵌套的组件使用相同的数据,则使用 redux 并将该数据存储在 redux-state 中是有意义的。这样整个App都可以全局访问,并且可以在不直接相关的组件之间共享。
例如考虑以下情况
const App = () =>
<Router>
<Route path="/" component=Home/>
<Route path="/mypage" component=MyComp/>
</Router>
如果 Home 和 MyComp 都想访问相同的数据,现在在这里。您可以通过 render
prop 调用 App 将数据作为 props 传递。然而,这很容易通过使用connect
函数将这两个组件连接到 Redux 状态来完成
const mapStateToProps = (state) =>
return
data: state.data
export connect(mapStateToProps)(Home);
MyComp
也是如此。也很容易配置更新相关信息的操作
此外,它还特别容易为您的应用程序配置 Redux,您可以将与相同事物相关的数据存储在各个 reducer 中。通过这种方式,您也可以模块化您的应用程序数据
【讨论】:
【参考方案3】:我对此的诚实建议。经验是:
Redux 很简单。它易于理解和扩展,但您应该将 Redux 用于某些特定用例。
由于 Redux 封装了您的应用程序,您可以考虑存储以下内容:
当前应用语言环境 当前认证用户 来自某处的当前令牌您在全球范围内需要的东西。 react-redux 甚至允许在组件上使用 @connect 装饰器。比如:
@connect(state => (
locale: state.locale,
currentUser: state.currentUser
))
class App extends React.Component
这些都是作为道具传递下来的,连接可以在应用程序的任何地方使用。虽然我建议只使用扩展运算符传递全局道具
<Navbar ...this.props />
您应用中的所有其他组件(或“页面”)都可以进行自己的封装状态。例如,用户页面可以做它自己的事情。
class Users extends React.Component
constructor(props)
super(props);
this.state =
loadingUsers: false,
users: [],
;
......
您可以通过 props 访问 locale 和 currentUser,因为它们是从 Container 组件传递下来的。
这个方法我用过很多次了,效果很好。
但是,既然你想先真正巩固 React 的知识,那么在做 Redux 之前,你可以将你的状态存储在***组件中,然后将其传递给子组件。
缺点:
您必须不断将它们向下传递到内部级别的组件中 要从内部级别组件更新状态,您必须传递更新状态的函数。这些缺点有点乏味且难以管理。这就是构建 Redux 的原因。
希望我能帮上忙。祝你好运
【讨论】:
谢谢,但如前所述,在充分了解 ReactJS 的基础之前,我不想介绍新概念。 @LucioB 我完全理解!您始终可以将全局内容保持在***组件状态。当您准备好引入 Redux 时,轻而易举! @LucioB 我在回答中添加了一些内容。以上是关于ReactJS - 提升状态与保持本地状态的主要内容,如果未能解决你的问题,请参考以下文章
ReactJS Redux State(全局状态)+ React.Context(本地状态)