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-upsingle- 是否正确真相的来源 概念。

作为可行的替代方案,我想为 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 实质上实现了shouldComponentUpdateshallowstate 的比较和props

根据文档:

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

但是,如果多个深度嵌套的组件使用相同的数据,则使用 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 - 状态数组中的重复对象保持链接

ReactJS Redux State(全局状态)+ React.Context(本地状态)

ReactJS 最佳实践,存储在隐藏字段、状态、本地存储中?

调用 setState 后 ReactJS 元素未更新

如何在反应中保持 Xstate 状态机中的状态?

我无法从状态渲染和对象,ReactJS 与功能组件