ReactJS - 提升状态与保持本地状态

IT技术 javascript reactjs create-react-app
2021-02-07 07:11:13

在我的公司,我们正在将 Web 应用程序的前端迁移到 ReactJS。我们正在使用 create-react-app(更新到 v16),没有 Redux。现在我被困在一个可以通过下图简化结构的页面上:

页面结构

三个组件(SearchableList、SelectableList 和 Map)显示的数据在componentDidMount()MainContainer方法中使用相同的后端请求进行检索此请求的结果然后存储在 MainContainer 的状态中,并具有或多或少这样的结构:

state.allData = {
  left: {
    data: [ ... ]
  },
  right: {
    data: [ ... ],
    pins: [ ... ]
  }
}

LeftContainerstate.allData.left从 MainContainer作为 prop 接收并传递props.left.data给 SearchableList,再次作为 prop。

RightContainerstate.allData.right从 MainContainer作为 prop 接收并传递props.right.data给 SelectableList 和props.right.pinsMap。

SelectableList 显示一个复选框以允许对其项目进行操作。每当对 SelectableList 组件的项目发生操作时,它可能会对 Map 引脚产生副作用。

我决定在 RightContainer 的状态中存储一个列表,该列表保留 SelectableList 显示的所有项目的 id;此列表作为props传递给 SelectableList 和 Map。然后我传递给 SelectableList 一个回调,每当进行选择时更新 RightContainer 内的 id 列表;新的 props 出现在 SelectableList 和 Map 中,因此render()在两个组件中都会被调用。

它工作正常并有助于将 SelectableList 和 Map 可能发生的所有事情保留在 RightContainer 内,但我问这对于提升状态单一来源的概念是否正确。

作为可行的替代方案,我想为MainContainer 中的_selected每个项目添加一个属性state.right.data,并将选择回调三个级别向下传递给 SelectableList,处理 MainContainer 中所有可能的操作。但是一旦发生选择事件,这最终将强制加载 LeftContainer 和 RightContainer,引入了实现逻辑的需要,例如shouldComponentUpdate()避免无用,render()尤其是在 LeftContainer 中。

从架构和性能的角度来看,哪个是/可能是优化此页面的最佳解决方案?

下面是我的组件的摘录,以帮助您了解情况。

主容器.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;

提前致谢!

3个回答

正如 React 文档所述

通常,多个组件需要反映相同的变化数据。我们建议将共享状态提升到它们最近的共同祖先。

对于在 React 应用程序中更改的任何数据,都应该有一个“真实来源”。通常,状态首先添加到需要它进行渲染的组件中。然后,如果其他组件也需要它,您可以将它提升到它们最近的共同祖先。您应该依赖自上而下的数据流,而不是尝试在不同组件之间同步状态。

提升状态涉及编写比双向绑定方法更多的“样板”代码,但作为一个好处,查找和隔离错误所需的工作更少。由于任何状态都“存在”于某个组件中,而该组件本身就可以改变它,因此大大减少了 bug 的表面积。此外,您可以实现任何自定义逻辑来拒绝或转换用户输入。

所以基本上你需要将那些状态提升到树上,这些状态也被 Siblings 组件用完。所以,你先执行,你存储selectedItems的状态下RightContainer是完全有理由和良好的做法,因为父母并不需要了解,这data是由两个共享child的组件RightContainer和这两个现在有一个源真相。

根据你的问题:

作为可行的替代方案,我想为 instate.right.data中的每个项目添加一个 _selected 属性MainContainer,并将 select 回调传递到三个级别SelectableList,处理所有可能的操作MainContainer

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

考虑到optimise on performance,您自己在谈论实现 a shouldComponentUpdate,但是您可以通过通过扩展来创建组件来避免这种情况,React.PureComponent它本质上是shouldComponentUpdate通过shallow比较state和来实现的props

根据文档:

如果您的 React 组件的render()函数在给定相同的 props 和 state 的React.PureComponen情况下呈现相同的结果,则在某些情况下您可以使用t 来提高性能。

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

例如考虑以下情况

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

现在在这里,如果 Home 和 MyComp 都想访问相同的数据。您可以通过renderprop调用数据作为来自 App 的 props 传递数据但是,通过使用connect类似函数将这两个组件连接到 Redux 状态可以轻松完成

const mapStateToProps = (state) => {
   return {
      data: state.data
   }
}

export connect(mapStateToProps)(Home);

对于MyComp. 还可以轻松配置更新相关信息的操作

此外,为您的应用程序配置 Redux 也特别容易,您将能够在各个减速器中存储与相同事物相关的数据。通过这种方式,您还可以module化您的应用程序数据

我对此的诚实建议。从经验来看是:

Redux 很简单。它很容易理解和扩展,但您应该将 Redux 用于某些特定用例。

由于 Redux 封装了您的应用程序,您可以考虑存储以下内容:

  • 当前应用程序区域设置
  • 当前认证用户
  • 来自某处的当前令牌

您在全球范围内需要的东西。react-redux 甚至允许在组件上使用@connect装饰器。所以像:

@connect(state => ({ 
   locale: state.locale,
   currentUser: state.currentUser
}))
class App extends React.Component

这些都作为props传递下来,连接可以在应用程序的任何地方使用。虽然我建议只通过传播操作符传递全局props

<Navbar {...this.props} />

应用程序中的所有其他组件(或“页面”)都可以执行自己的封装状态。例如用户页面可以做它自己的事情。

class Users extends React.Component {
  constructor(props) {
    super(props);

    this.state = {
      loadingUsers: false,
      users: [],
    };
  }
......

您将通过 props 访问 locale 和 currentUser,因为它们是从 Container 组件传递过来的。

这种方法我已经做过多次并且有效。

但是,由于您想真正巩固 React 的知识,因此在使用 Redux 之前,您可以将状态存储在顶级组件上并将其传递给子组件。

缺点:

  • 您将不得不继续将它们传递到内部级别的组件中
  • 要从内部级别的组件更新状态,您必须传递更新状态的函数。

这些缺点有点乏味且难以管理。这就是 Redux 被构建的原因。

希望我有所帮助。祝你好运

@LucioB 我在答案中添加了一点。
2021-03-15 07:11:13
@LucioB 我完全理解!您始终可以将全局内容保留在顶级组件状态。当您准备好介绍 Redux 时,轻而易举!
2021-03-21 07:11:13
谢谢,但如前所述,在对 ReactJS 的基础有很好的了解之前,我不想介绍新概念。
2021-04-12 07:11:13

通过使用 Redux,您可以避免此类回调并将整个状态保持在一个单一的存储中 - 所以让你的父组件连接组件 - 并使左右组件成为愚蠢的 - 并且只需将你从父组件传递给子组件的props - 然后你在这种情况下不必担心回调。

我想避免使用 Redux,我知道它可以解决很多与状态管理相关的问题,但正如D.Abramov(Redux 的合著者)在此处所述,最好先学习以 React 的方式思考,因为Redux 引入了一个新的复杂度。同一篇文章还说:最后,不要忘记,您可以在不使用 Redux 的情况下应用来自 Redux 的想法。例如,考虑一个具有本地状态的 React 组件。
2021-03-16 07:11:13
我认为这是正确的想法 - 使用回调没有错,只要它仍然相当容易理解,它似乎就在这里。Redux 非常适合跨越大层次结构的非常复杂的状态;对于像这样简单的事情,它可能会让人感觉像是很多不必要的仪式。
2021-03-27 07:11:13