React: 当父组件重新渲染时,如何防止子组件的重复挂载

10

我有一个组件,Split,它需要两个子元素。第一个子元素将显示在屏幕的左侧,第二个子元素将显示在右侧。如果屏幕宽度低于某个点,则只显示右侧,而左侧将从DOM中删除。

示例子元素可以是Sidebar组件和Content组件。对于移动设备,我不想显示菜单,但有一个特殊的移动菜单,我会弹出它。

我的问题是:如何在不卸载并重新挂载Content组件的情况下删除Sidebar组件?

我的Content组件在componentDidMount上获取数据,并且我不想再次获取或重新挂载(从而丢弃用户输入)。

基本上我有这样的东西:

<Split>
  <Sidebar/>
  <Content/>
</Split>

Split 的渲染方法看起来大致如下:

let children;
let firstChild = this.props.children[0];
let lastChild = this.props.children.pop();
if (this.state.responsive === 'singleColumn') {
  children = (
    <div>
      <div style={{display: 'none'}}>{firstChild}</div>
      {lastChild}
    </div>
  );
} else {
  children = (
    <div>
      {firstChild}
      {lastChild}
    </div>
  );
}

return (
  <div>
    {children}
  </div>
);

即使 {lastChild} 总是被渲染,无论如何,每次分割重新渲染时它仍然会被卸载和重新挂载!

即使有一个看起来像这样的渲染:

return (
  <div>
    {this.props.children.pop()}
  </div>
);

会导致最后一个子元素(它永远不会改变)在呈现之前被卸载和重新挂载。

如果我改为修改Split并将始终存在于DOM中的组件作为属性传递,如下所示:

<Split staticComponent={<Content />}>
  <Sidebar />
</Split>

它正常运行。但为什么仅弹出最后一个子节点时不起作用,如此 {this.props.children.pop()},而不是这样 {this.props.staticComponent}

有没有合理的方法解决这个问题?


我现在明白你的意思了,因此删除了,你尝试在这里使用CSS了吗?这更多是一个与CSS有关的问题,当屏幕大小达到一定程度时,你需要更改该子元素的样式为display:none。希望能帮到你。 - omarjmh
看看这篇文章,中间部分讲述了基于屏幕大小的类似场景演示。 https://medium.com/@jviereck/modularise-css-the-react-way-1e817b317b04#.wew0pfsyh - omarjmh
好的,问题在于Split来自库,它非常好用,在不写任何CSS的情况下完美地工作,除了这个问题。因此,纯CSS解决方案并不是我想要的。我希望React能够移动{lastChild}组件,而不是将其从DOM中删除和插入。 - Oscar Linde
我明白了,如果我不使用库来完成它,我知道该如何去做。但是必须有一些方法来避免卸载<Content>组件,因为无论设置什么状态或属性,它始终由Split渲染。 - Oscar Linde
这个库有媒体查询吗?如果没有,就试试看会不会出问题,为什么不呢? - omarjmh
显示剩余3条评论
3个回答

3

终于成功解决了问题!我重新编写了Split的渲染,大致如下:

let left;
if (this.state.responsive !== 'singleColumn') {
  left = this.props.children.slice(0, -1);
}

return (
  <div ref="split" className={classes.join(' ')}>
    {left}
    {this.props.children[this.props.children.length-1]}
  </div>
);

这样最后一个子节点总是被渲染出来。显然,pop()没有起作用,因为我修改了原始的子节点数组,从而触发了奇怪的行为。


0
你可以在组件的生命周期中使用shouldComponentUpdate函数。如果它返回false,根据文档:

如果shouldComponentUpdate返回false,则render()将完全跳过,直到下一次状态更改。此外,componentWillUpdate和componentDidUpdate也不会被调用。


3
这只是防止渲染。我想要的是防止<Split>卸载并重新挂载我的<Content>组件,因为这样会使其失去状态并在componentDidMount上再次获取数据。 - Oscar Linde
所以你只想影响<Sidebar>组件? 我猜这应该在<Sidebar>组件内部处理,你可以通过<Sidebar>的props传递一个函数,当从<Split>调用时,<Sidebar>将隐藏自身(在渲染时),并且它将是唯一受影响的,因此不会影响<Content>。 - northsideknight
没错。因为它在内部执行的操作类似于:if (shouldShowSidebar) { left=this.props.children[0]; } return (<div>{left}{this.props.children.pop()}</div>); - Oscar Linde
如果你确实想使用这个库,我建议在componentDidMount函数中使用条件语句,这样你仍然可以渲染,但不必浪费时间获取已经拥有的资源(或者在其中执行任何需要的操作)。但是,你似乎已经足够了解这个库,可以自己编写所需的代码,而不必在这种情况下使用该库,因为它似乎并不完全符合你的要求。 - northsideknight
你说得对。但是组件状态在卸载时被清除的问题仍然存在。所以我必须暂时保存自己想要避免的数据。我不太确定如何以明智的方式自己编写它。我在原始问题中更新了一个不太明智的方法。通过 <Split> 上的属性传递将始终呈现的组件,但在我看来这并不理想。 - Oscar Linde
显示剩余2条评论

0

这可以通过生命周期方法 shouldComponentUpdate 来实现:

shouldComponentUpdate: function(nextProps, nextState) {
  return this.props.value !== nextProps.value;
}

如果返回false,则组件内部的逻辑将不会更新。
请查看文档:https://facebook.github.io/react/docs/advanced-performance.html

1
Split组件应该仍然重新渲染。但是最后传递的子元素不应该从DOM中删除。所以我不认为shouldComponentUpdate能帮到我,很遗憾 :( - Oscar Linde
这应该放在你想控制的组件中,而不是拆分。 - omarjmh
3
shouldComponentUpdate只会阻止组件的重新渲染。我想要阻止Split组件卸载我的子组件。 - Oscar Linde

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接