ReactJS - 提升状态与保持本地状态 [英] ReactJS - Lifting state up vs keeping a local state

查看:30
本文介绍了ReactJS - 提升状态与保持本地状态的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

在我的公司,我们正在将 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-upsingle-真相来源概念.

作为可行的替代方案,我想在 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 中的第一个实现是完全合理的,也是一个很好的方法,因为父级不需要知道这个 dataRightContainer 的两个 child 组件共享,这两个组件现在有一个单一的事实来源.

根据您的问题:

<块引用>

作为可行的替代方案,我想添加一个 _selected 属性到MainContainerstate.right.data 中的每一项并传递选择回调三级到SelectableList,处理所有MainContainer

中可能的操作

我不同意这是比第一种更好的方法,因为您 MainContainer 不需要知道 selectedItems 或处理任何更新.MainContainer 对这些状态没有做任何事情,只是将其传递下去.

考虑到优化性能,您自己谈论实现shouldComponentUpdate,但您可以通过扩展React.PureComponent 基本上实现了 shouldComponentUpdateshallow stateprops 的比较.

根据文档:

<块引用>

如果你的 React 组件的 render() 函数呈现相同的结果给定相同的 props 和 state,你可以使用 React.PureComponent在某些情况下性能提升.

然而,如果多个深度嵌套的组件正在使用相同的数据,那么使用 redux 并将该数据存储在 redux-state 中是有意义的.这样就可以对整个App全局访问,并且可以在不直接相关的组件之间共享.

例如考虑以下情况

const App = () =>{<路由器><Route path="/" component={Home}/><Route path="/mypage" component={MyComp}/></路由器>}

现在如果 Home 和 MyComp 都想访问相同的数据.您可以通过 render prop 调用它们,将数据作为道具从 App 传递.但是,通过使用 connect 函数(如

)将这两个组件连接到 Redux 状态,可以轻松完成

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 in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in 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.

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 use React.PureComponent 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屋!

查看全文
相关文章
前端开发最新文章
热门教程
热门工具
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆