ReactJS - 提升状态与保持本地状态 [英] ReactJS - Lifting state up vs keeping a local state
问题描述
在我的公司,我们正在将Web应用程序的前端迁移到ReactJS。
我们正在使用create-react-app(更新到v16),没有Redux。
现在我停留在一个页面上,可以通过以下图像简化结构:
At my company we're migrating the front-end of a web application to ReactJS. We are working with create-react-app (updated to v16), without Redux. Now I'm stuck on a page which structure can be simplified by the following image:
在 componentDidMount()
中使用相同的后端请求检索三个组件(SearchableList,SelectableList和Map)显示的数据MainContainer的方法。然后,此请求的结果将存储在MainContainer的状态中,并且具有或多或少的结构:
The data displayed by the three components (SearchableList, SelectableList and Map) is retrieved with the same backend request in the componentDidMount()
method of MainContainer. The result of this request is then stored in the state of MainContainer and has a structure more or less like this:
state.allData = {
left: {
data: [ ... ]
},
right: {
data: [ ... ],
pins: [ ... ]
}
}
LeftContainer收到prop <来自MainContainer的code> state.allData.left 并将 props.left.data
传递给SearchableList,再次作为prop。
LeftContainer receives as prop state.allData.left
from MainContainer and passes props.left.data
to SearchableList, once again as prop.
RightContainer从MainContainer接收为 state.allData.right
并传递 props.right.data
到SelectableList, props.right.pins
到Map。
RightContainer receives as prop state.allData.right
from MainContainer and passes props.right.data
to SelectableList and props.right.pins
to Map.
SelectableList显示一个复选框允许对其项目采取行动。每当在一个SelectableList组件项上发生动作时,它可能会对Map引脚产生副作用。
SelectableList displays a checkbox to allow actions on its items. Whenever an action occur on an item of SelectableList component it may have side effects on Map pins.
我决定在RightContainer状态下存储一个保留所有内容的列表SelectableList显示的项目ID;此列表作为道具传递给SelectableList和Map。然后我向SelectableList传递一个回调函数,无论何时进行选择都会更新RightContainer中的id列表;新道具到达SelectableList和Map,因此在两个组件中都会调用 render()
。
I've decided to store in the state of RightContainer a list that keeps all the ids of items displayed by SelectableList; this list is passed as props to both SelectableList and Map. Then I pass to SelectableList a callback, that whenever a selection is made updates the list of ids inside RightContainer; new props arrive in both SelectableList and Map, and so render()
is called in both components.
它的工作原理很好,并有助于保持RightContainer中SelectableList和Map可能发生的一切,但我问这是否正确提升状态和单一来源的真相概念。
It works fine and helps to keep everything that may happen to SelectableList and Map inside RightContainer, but I'm asking if this is correct for the lifting-state-up and single-source-of-truth concepts.
作为可行的替代方案,我想到为<$中的每个项目添加 _selected
属性在MainContainer中c $ c> state.right.data 并将select回调三级传递给SelectableList,处理MainContainer中的所有可能操作。但是一旦选择事件发生,这将最终强制加载LeftContainer和RightContainer,引入了实现逻辑的需要,如 shouldComponentUpdate()
,以避免无用的 render()
特别是在LeftContainer中。
As feasible alternative I thought of adding a _selected
property to each item in state.right.data
in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in MainContainer. But as soon as a selection event occurs this will eventually force the loading of LeftContainer and RightContainer, introducing the need of implementing logics like shouldComponentUpdate()
to avoid useless render()
especially in LeftContainer.
哪个/可能是从架构和性能优化此页面的最佳解决方案观点?
下面是我的组件摘录,以帮助您了解情况。
Below you have an extract of my components to help you understand the situation.
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;
提前致谢!
推荐答案
作为React docs state
As React docs state
通常,有几个组件需要反映相同的变化数据。我们b $ b建议将共享状态提升到最接近的公共
祖先。
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
任何数据都应该有一个真实来源在React应用程序中更改
。通常,状态首先被添加到需要渲染的
组件中。然后,如果其他组件也需要
,你可以将它提升到最近的共同祖先。相反,
试图在不同组件之间同步状态,你应该依赖于自上而下的数据流。
There should be a single "source of truth" for any data that changes in a React application. Usually, the state is first added to the component that needs it for rendering. Then, if other components also need it, you can lift it up to their closest common ancestor. Instead of trying to sync the state between different components, you should rely on the top-down data flow.
提升状态涉及写更多样板代码比双向
绑定方法,但作为一个好处,它需要更少的工作来查找和
隔离错误。由于某个组件中的任何状态都存在并且单独的
组件可以改变它,因此错误的表面积大大减少了
。此外,您可以实现任何自定义逻辑来拒绝或
转换用户输入。
Lifting state involves writing more "boilerplate" code than two-way binding approaches, but as a benefit, it takes less work to find and isolate bugs. Since any state "lives" in some component and that component alone can change it, the surface area for bugs is greatly reduced. Additionally, you can implement any custom logic to reject or transform user input.
所以基本上你需要解除这些状态正在使用兄弟姐妹组件的树。所以你首先实现存储 selectedItems
的地方作为 RightContainer
中的状态是完全合理的,这是一个很好的方法,因为父母不需要知道这个数据
由<$ c的两个子
组件共享$ c> RightContainer ,这两个现在只有一个事实来源。
So essentially you need to lift those state up the tree that are being used up the Siblings component as well. So you first implementation where you store the selectedItems
as a state in the RightContainer
is completely justified and a good approach, since the parent doesn't need to know about and this data
is being shared by the two child
components of RightContainer
and those two now have a single source of truth.
根据你的问题:
作为可行的替代方案,我想在
state.right.data
中为每个项目添加一个_selected属性到
在MainContainer
中,将select
回调三个级别传递给SelectableList
,处理所有
MainContainer中的可能操作
As feasible alternative I thought of adding a _selected property to each item in
state.right.data
inMainContainer
and pass the select callback three levels down toSelectableList
, handling all the possible actions inMainContainer
我不同意这是更好的方法比第一个方法,因为你 MainContainer
不需要知道 selectedItems
或处理任何更新。 MainContainer
没有对这些状态做任何事情而只是将其传递下去。
I wouldn't agree that this is a better approach than the first one, since you MainContainer
doesn't need to know the selectedItems
or handler any of the updates. MainContainer
isn't doing anything about those states and is just passing it down.
考虑优化性能
,你自己谈论实现 shouldComponentUpdate
,但您可以通过扩展 <$ c $来创建组件来避免这种情况。 c> React.PureComponent 基本上实现了 shouldComponentUpdate
,其中浅
比较州
和道具
。
Consider to optimise on performance
, you yourself talk about implementing a shouldComponentUpdate
, but you can avoid that by creating your components by extending React.PureComponent
which essentially implements the shouldComponentUpdate
with a shallow
comparison of state
and props
.
根据docs:
如果您的React组件的
render()
函数呈现相同的结果
给出相同的道具和状态,在某些情况下,你可以使用React.PureComponen
t获得
的性能提升。
If your React component’s
render()
function renders the same result given the same props and state, you can useReact.PureComponen
t for a performance boost in some cases.
但是,如果多个深度嵌套的组件正在使用相同的数据,那么使用redux并将该数据存储在redux-state中是有意义的。通过这种方式,整个应用程序可以全局访问,并且可以在不直接相关的组件之间共享。
However if multiple deeply nested components are making use of the same data, it makes sense to make use of redux and store that data in the redux-state. In this way it is globally accessible to the entire App and can be shared between components that are not directly related.
例如,请考虑以下情况
const App = () => {
<Router>
<Route path="/" component={Home}/>
<Route path="/mypage" component={MyComp}/>
</Router>
}
现在,如果Home和MyComp都想访问相同的数据。您可以通过 render
prop调用数据作为道具从App传递。但是,通过使用 connect
函数将这两个组件连接到Redux状态很容易,例如
Now here if both Home and MyComp want to access the same data. You could pass the data as props from App by calling them through render
prop. However it would easily be done by connecting both of these components to Redux state using a connect
function like
const mapStateToProps = (state) => {
return {
data: state.data
}
}
export connect(mapStateToProps)(Home);
同样适用于 MyComp
。此外,它还可以轻松配置更新相关信息的操作
and similarly for MyComp
. Also its easy to configure actions for updating relevant informations
此外,它还可以为您的应用程序配置Redux,并且您可以将相同内容的数据存储在个人减速器。通过这种方式,您也可以模块化您的应用程序数据
Also its particularly easy to configure Redux for your application and you would be able to store data related to the same things in the individual reducers. In this way you would be able to modularise your application data as well
这篇关于ReactJS - 提升状态与保持本地状态的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!