【问题标题】:In react, if I set shouldComponentUpdate to false, will any state I send to children be updated?作为反应,如果我将 shouldComponentUpdate 设置为 false,我发送给孩子的任何状态都会更新吗?
【发布时间】:2017-11-21 14:22:35
【问题描述】:

我有以下场景:

1) 有一个父组件“ModuleListContainer”。

2) 一个模块(在模块列表中,也是一个子组件,但在此上下文中不感兴趣)在将鼠标悬停在列表中的模块项上时被选中。

3) 将鼠标悬停在模块上时,菜单应显示在模块的角落。

4) 选择模块时不应更新整个父组件,因为它可能是很长的模块列表,这就是为什么我在更新应该选择哪个模块时设置shouldComponentUpdate = false

5) 菜单在父组件加载时加载,鼠标悬停在模块上时仅更新其位置。

这是父组件(简体)...

class ModuleListContainer extends Component {

    constructor(props) {
        super(props);
        this.state = {
            selectingModule: false,
            currentlySelectedModule: nextProps.currentModule
        }
    }

    shouldComponentUpdate(nextProps, nextState) {
        if (nextState.selectingModule === true) {
            this.setState({
                selectingModule: false,
                currentlySelectedModule: null
            })
            return false;
        }
        return true;
    }

    mouseEnterModule = (e, moduleItem) => {
        const menu = document.getElementById('StickyMenu');
        const menuPosition = calculateModuleMenuPosition(e.currentTarget);

        if (moduleItem.ModuleId !== this.props.currentModuleId) {
            this.props.actions.selectModule(moduleItem);
            this.setState({
               selectingModule: true
            });
        }

        menu.style.top = menuPosition.topPos + 'px';
        menu.style.left = menuPosition.leftPos + 'px';
    }

    render() {
        return (
            <div>
                <section id="module-listing">
                    {/* ... list of mapped modules with mouseEnterModule event */}
                </section>

                <ModuleMenu {... this.props} currentlySelectedModule={this.state.currentlySelectedModule} />
            </div>
        );
    }
}

这是菜单组件(简化版)...

class ModuleMenu extends Component {

    constructor(props) {
        super(props);
        this.state = {
            currentModule: this.props.currentlySelectedModule
        };
    }

    clickMenuButton = () => {
        console.log('CURRENT MODULE', this.state.currentModule);
    }

    render() {
        return (
            <div id="StickyMenu">
                <button type="button" onClick={this.clickMenuButton}>
                    <span className="fa fa-pencil"></span>
                </button>
            </div>
        );
    }
}

当我在我的菜单组件中尝试从状态中console.log 当前模块时,我不断得到null

我的问题是这是不是因为......

  1. 我已将 shouldComponentUpdate 设置为 false 并且菜单的状态未更新?

  2. 还是因为我没有重新渲染整个组件?

  3. 还是因为我把菜单和父组件一起加载了 并且在选择模块时不会重新渲染?

  4. 或者可能是上述几种情况的组合?

反应文档 (https://reactjs.org/docs/react-component.html) 说:

返回 false 不会阻止子组件重新渲染 当他们的状态发生变化时。

因此,我希望不是上述情况,因为我真的不想在选择模块时重新渲染整个组件。

【问题讨论】:

    标签: javascript reactjs


    【解决方案1】:

    在这种情况下,您的孩子状态不会改变,您只是改变了父母的状态。您可能应该做的是将组件的渲染方法拆分为两个组件:

    render() {
        return (
            <div>
                <NoUpdateComponent someProps={this.props.someProps}/>
    
                <ModuleMenu {... this.props} currentlySelectedModule={this.state.currentlySelectedModule} />
            </div>
        );
    }
    

    然后在你的第一个代价高昂的组件中,使用shouldComponentUpdate 方法来防止它重新渲染

    【讨论】:

      【解决方案2】:

      我认为在您的代码中还有其他问题需要解决,然后再寻找实用的解决方案,从您对 shouldComponentUpdate() 的使用开始。

      Official doc 说:

      使用 shouldComponentUpdate() 让 React 知道组件的输出是否不受当前状态或道具变化的影响。默认行为是在每次状态更改时重新渲染,并且在绝大多数情况下,您应该依赖默认行为。 当接收到新的道具或状态时,应该在渲染之前调用 shouldComponentUpdate()。默认为真。初始渲染或使用 forceUpdate() 时不会调用此方法。

      如果你在shouldComponentUpdate() 函数中执行setState() 调用,它可能会起作用,但本质上你是在告诉 React 在知道在此执行中是否应该渲染之前开始一个新的渲染周期。 还要记住setState()is not guaranteed要立即执行:

      将 setState() 视为更新组件的请求而不是立即命令。为了更好地感知性能,React 可能会延迟它,然后一次更新多个组件。 React 不保证立即应用状态更改。

      此外(从代码中不是很清楚,所以我猜)你正在为ModuleMenu 分离组件和 DOM 对象:它的表示必须由状态或道具引导,在这里你使用 HTMLElement.style.x 语法来设置其表示属性。

      我将重构 ModuleListContainer 以在其状态中存储一个表示 ModuleMenu 属性的对象,该对象将作为道具传递给 ModuleMenu 组件,如下所示:

      moduleMenu {
          currentModuleId: ... ,
          top: ... ,
          left: ...
      }
      

      并在mouseEnterModule处理程序中设置状态:

      mouseEnterModule = (e, moduleItem) => {
          const menuPosition = calculateModuleMenuPosition(e.currentTarget);
          if (moduleItem.ModuleId !== this.props.currentModuleId) {
              this.props.actions.selectModule(moduleItem);
              this.setState({
                  moduleMenu: {
                    currentModuleId: moduleItem.ModuleId,
                    left: menuPosition.leftPos + 'px',
                    top: menuPosition.topPos + 'px'
                  }
              });
          }
      }
      

      然后ModuleMenu可以这样获取新的位置:

      <div id="StickyMenu">
        style={{
          left: this.props.left,
          top: this.props.top
        }}
      >
        ...
      </div>
      

      当然,您仍然可以使用shouldComponentUpdate() 来确定应该更新列表中的哪些模块,但这次在比较(我想再一次)项目的ID 之后只返回一个布尔值;避免过多调用setState() 方法。

      希望对您有所帮助!

      【讨论】:

      • 谢谢。我正在按照您的建议使用状态设置顶部/左侧位置,但这会导致整个组件的重新渲染,所以我更改了它。当我得到其他解决方案的工作时,我会尝试将其改回。也感谢您对 shouldComponentUpdate 中状态的提醒,我也会尝试使用不同的解决方案。
      猜你喜欢
      • 1970-01-01
      • 2018-10-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-05-27
      • 1970-01-01
      • 2020-04-06
      相关资源
      最近更新 更多