ReactJS - 提升状态与保持本地状态 [英] ReactJS - Lifting state up vs keeping a local state
问题描述
在我的公司,我们正在将 Web 应用程序的前端迁移到 ReactJS.我们正在使用 create-react-app(更新到 v16),没有 Redux.现在我被困在一个可以通过下图简化结构的页面上:
在MainContainer的componentDidMount()
方法中,通过相同的后端请求检索三个组件(SearchableList、SelectableList和Map)显示的数据.这个请求的结果然后被存储在 MainContainer 的状态中,其结构或多或少是这样的:
state.allData = {剩下: {数据: [ ... ]},对: {数据: [ ... ],引脚: [ ... ]}}
LeftContainer 从 MainContainer 接收作为 prop state.allData.left
并将 props.left.data
传递给 SearchableList,再次作为 prop.
RightContainer 从 MainContainer 接收作为 prop state.allData.right
并将 props.right.data
传递给 SelectableList 和 props.right.pins
到地图.
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 扩展 React.Component {构造函数(道具){超级(道具);this.state = {所有数据:{}};}componentDidMount() {拿来( ... ).then((res) => {this.setState({所有数据:资源});});}使成为() {返回 (<div className="main-container"><LeftContainer left={state.allData.left}/><RightContainer right={state.allData.right}/>
);}}导出默认 MainContainer;
RightContainer.js
class RightContainer 扩展 React.Component {构造函数(道具){超级(道具);this.state = {所选项目:[ ... ]};}onDataSelection(e) {const itemId = e.target.id;//... 处理 itemId 和 selectedItems ...}使成为() {返回 (<div className="main-container"><可选列表数据={props.right.data}onDataSelection={e =>this.onDataSelection(e)}selectedItems={this.state.selectedItems}/><地图pin={props.right.pins}selectedItems={this.state.selectedItems}/>
);}}导出默认的 RightContainer;
提前致谢!
As React docs state
<块引用>通常,多个组件需要反映相同的变化数据.我们建议将共享状态提升到最接近的公共状态祖先.
任何变化的数据都应该有一个单一的真实来源"在 React 应用程序中.通常,首先将状态添加到需要它进行渲染的组件.那么,如果其他组件也需要它,你可以把它举到他们最近的共同祖先那里.反而尝试在不同组件之间同步状态,你应该依靠自上而下的数据流.
提升状态涉及编写比双向更多的样板"代码具有约束力的方法,但作为一个好处,它需要更少的工作来查找和隔离错误.由于任何状态存在"于某个组件中,并且单独的组件可以改变它,错误的表面积很大减少.此外,您可以实现任何自定义逻辑来拒绝或转换用户输入.
因此,本质上,您还需要将那些被 Siblings 组件使用的状态提升到树上.因此,您将 selectedItems
作为状态存储在 RightContainer
中的第一个实现是完全合理的,也是一个很好的方法,因为父级不需要知道这个 data
由 RightContainer
的两个 child
组件共享,这两个组件现在有一个单一的事实来源.
根据您的问题:
<块引用>作为可行的替代方案,我想添加一个 _selected 属性到MainContainer
中 state.right.data
中的每一项并传递选择回调三级到SelectableList
,处理所有MainContainer
我不同意这是比第一种更好的方法,因为您 MainContainer
不需要知道 selectedItems
或处理任何更新.MainContainer
对这些状态没有做任何事情,只是将其传递下去.
考虑到优化性能
,您自己谈论实现shouldComponentUpdate
,但您可以通过扩展React.PureComponent
基本上实现了 shouldComponentUpdate
和 shallow
state
和 props
的比较.
根据文档:
<块引用>如果你的 React 组件的 render()
函数呈现相同的结果给定相同的 props 和 state,你可以使用 React.PureComponen
t在某些情况下性能提升.
然而,如果多个深度嵌套的组件正在使用相同的数据,那么使用 redux 并将该数据存储在 redux-state 中是有意义的.这样就可以对整个App全局访问,并且可以在不直接相关的组件之间共享.
例如考虑以下情况
const App = () =>{<路由器><Route path="/" component={Home}/><Route path="/mypage" component={MyComp}/></路由器>}
现在如果 Home 和 MyComp 都想访问相同的数据.您可以通过 render
prop 调用它们,将数据作为道具从 App 传递.但是,通过使用 connect
函数(如
const mapStateToProps = (state) =>{返回 {数据:state.data}}出口连接(mapStateToProps)(家);
和 MyComp
类似.还可以轻松配置更新相关信息的操作
此外,为您的应用程序配置 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:
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 receives as prop state.allData.left
from MainContainer and passes props.left.data
to SearchableList, once again as prop.
RightContainer receives as prop state.allData.right
from MainContainer and passes props.right.data
to SelectableList and props.right.pins
to 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.
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.
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.
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.
Which is / could be the best solution to optimise this page from an architectural and performance point of view?
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;
Thanks in advance!
As React docs state
Often, several components need to reflect the same changing data. We recommend lifting the shared state up to their closest common ancestor.
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.
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.
As per your question:
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
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.
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
.
According to the docs:
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.
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.
For example consider the following case
const App = () => {
<Router>
<Route path="/" component={Home}/>
<Route path="/mypage" component={MyComp}/>
</Router>
}
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);
and similarly for MyComp
. Also its easy to configure actions for updating relevant informations
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屋!