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

时间:2017-10-05 21:14:17

标签: javascript reactjs create-react-app

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

Page structure

在MainContainer的componentDidMount()方法中,使用相同的后端请求检索由三个组件(SearchableList,SelectableList和Map)显示的数据。然后,此请求的结果将存储在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列表;新的道具到达SelectableList和Map,因此在两个组件中都会调用render()

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

作为可行的替代方案,我想到为MainContainer中的_selected中的每个项添加state.right.data属性,并将选择回调三个级别传递给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;

提前致谢!

3 个答案:

答案 0 :(得分:9)

作为React docs state

  

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

     

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

     

提升状态涉及编写比双向更多的“样板”代码   绑定方法,但作为一种好处,它需要较少的工作来查找和   隔离bug。因为任何一个国家都“生活”在一些组成部分中   单独的组件可以改变它,bug的表面积很大   降低。此外,您可以实现任何自定义逻辑来拒绝或   转换用户输入。

所以基本上你需要将那些状态提升到正在用尽兄弟姐妹组件的树上。因此,首先实现将selectedItems存储为RightContainer状态的data完全合理并且是一种好的方法,因为父级不需要知道这个child正在由RightContainer的两个state.right.data组件共享,而这两个组件现在只有一个事实来源。

根据你的问题:

  

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

中的可能操作

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

考虑到shouldComponentUpdate,您自己谈论实施shouldComponentUpdate,但您可以通过扩展React.PureComponent创建您的组件来避免这种情况,{{3}}基本上实现了shallow stateprops的比较render()

根据文件:

  

如果您的React组件的React.PureComponen函数呈现相同的结果   给定相同的道具和状态,您可以使用const App = () => { <Router> <Route path="/" component={Home}/> <Route path="/mypage" component={MyComp}/> </Router> } t来表示   在某些情况下性能提升。

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

例如,考虑以下情况

render

现在,如果Home和MyComp都想访问相同的数据。您可以通过connect prop调用数据作为道具从App传递。但是,通过使用const mapStateToProps = (state) => { return { data: state.data } } export connect(mapStateToProps)(Home); 函数(如

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

,同样适用于currentIndexChanged。此外,它还可以轻松配置更新相关信息的操作

此外,为您的应用程序配置Redux特别容易,并且您可以在各个Reducer中存储与相同内容相关的数据。通过这种方式,您也可以将应用程序数据模块化

答案 1 :(得分:7)

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

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建成的原因。

希望我能帮到你。祝你好运

答案 2 :(得分:1)

通过使用Redux,你可以避免这样的回调并在一个商店中维护整个状态 - 所以让你的父组件连接组件 - 并使左右组件变得愚蠢 - 并且只是传递你从父母到孩子的道具 - 在这种情况下你不必担心回调。