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

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

问题描述

在我的公司,我们正在将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 in MainContainer and pass the select callback three levels down to SelectableList, handling all the possible actions in MainContainer

我不同意这是更好的方法比第一个方法,因为你 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 use React.PureComponent 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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆