React-Redux - 如何更新一段状态而不会对不相关的组件造成不必要的重新渲染

时间:2017-05-03 23:06:32

标签: javascript reactjs redux react-redux

我正在努力以简洁的方式提出这个问题。我的应用程序遇到了一些重大的性能问题。我已经安装了用于反应的Perf附加工具,可以看出问题所在,但我不确定解决问题的最佳方法。

我认为它可能与ReSelect有关......但需要一些指导,从哪里开始。

我有一个组件可以呈现许多其他组件。这使用size-me(计算浏览窗口的大小)和react-grid-layout(布局每个组件并允许更改其定位)。这是资源密集型的,所以我不能不必要地发生这种情况。

用户可以单击按钮打开模态窗口(添加或编辑正在网格中呈现的组件)。

问题:当模态窗口打开时,底层组件重新渲染,导致size-me和react-grid-layout重新渲染,从而导致模式“抽搐”打开!

这是整个状态树: This is the entire state tree

当我打开模态时,这是状态的唯一部分: This is the only part of the state that changes when I open the modal

size-me和react-grid-layout的东西是从状态树的formEngine.form部分渲染状态,但是当对树的formEngine.addComponent部分进行状态更新时它正在被重新渲染< / p>

以下是性能日志: Here are the performance logs

正如您所看到的,有一些浪费的渲染正在发生,并且这只会根据用户决定添加到表单的嵌套布局组件的数量逐步增长...

因此,为了避免这个问题变得过于复杂,请先问一下

  1. 打开模式时如何防止底层页面重新渲染?
  2. 当fromEngine.addComponent被修改时,为什么触发formEngine.form的组件会被重新渲染?
  3. 谢谢。

    编辑1:

    我不确定这是否相关,但为了回答评论,我添加了这段代码。 AddFormComponent是一个猛烈打开的模态。

    Form.js:

    const Form = (props) => (
      <div className="form-engine">
        <div className="card-block" style={{position: "relative"}}>
          {
            props.editMode && 
            <div className="nula-form-controls">
              <AddFormComponent parentId={"root"} />
            </div>
          }               
          {
            props.form.components.root.childComponentIds.length > 0 ?
              <LayoutComponent componentKey={"root"} />
            : 
              <EmptyGridLayout />
          }
        </div>
      </div>
    )
    

    LayoutComponent.js:

    import React from 'react'
    import _ from 'lodash'
    import SizeMe from 'react-sizeme'
    import { Responsive as ResponsiveReactGridLayout } from 'react-grid-layout'
    import 'react-grid-layout/css/styles.css'
    import 'react-resizable/css/styles.css'
    
    import FormComponent from '../containers/FormComponent'
    import NestedLayoutComponent from '../containers/LayoutComponent'
    
    import AddFormComponent from '../containers/AddFormComponent'
    import LayoutComponentEditor from '../containers/LayoutComponentEditor'
    
    //Setup SizeMe Configuration
    let sizeMeConfig = {
      monitorWidth: true
    }
    let sizeMeHOC = SizeMe(sizeMeConfig)
    
    //Wrap ResponsiveReactGridLayout in sizeMeHOC so that it is aware of it's width
    var GridLayout = ResponsiveReactGridLayout
    GridLayout = sizeMeHOC(GridLayout)
    
    const LayoutComponent = (props) => (
      <div>
        <GridLayout
          cols={props.cols}
          className={props.className}
          breakpoints={props.breakpoints}
          rowHeight={props.rowHeight}
          draggableCancel={props.draggableCancel}
          layouts={props.layouts}
          isDraggable={props.isDraggable}
          isResizable={props.isResizable}
          onLayoutChange={(currentLayout, allLayouts) => props.handleLayoutChange(props.componentKey, currentLayout, allLayouts)}
          width={props.size.width}
        >
          {
            //Map out any child layouts
            props.childComponents.map((component) => {
              if (component.type === "card") {
                return (
                  <div className={"card card-outline-" + component.color} key={component.key}>
                    <div className={"card-header card-" + component.color}>
                      {component.header}
                    </div>
                    <div className="card-block" style={{overflowY: "auto", position: "relative"}}>
                      {
                        //Hide if editMode={false}
                        props.editMode && 
                          <div className="nula-card-controls">
                            <LayoutComponentEditor path={component.key} />
                            <a href="#" className="text-danger" title="Remove"><span className="fa fa-trash" /></a>
                            <AddFormComponent parentId={component.key} />
                          </div>
                      }                  
                      <NestedLayoutComponent componentKey={component.key} />
                    </div>               
                  </div>
                )
              }
              else if (component.type === "fieldGroup") {
                return (
                  <div className="card" key={component.key}>
                    <div className="card-block pl-0 pr-0 pt-2 pb-0" style={{overflowY: "auto"}}>
                      {
                        //Hide if editMode={false}
                        props.editMode && 
                          <div className="nula-fieldgroup-controls">
                            <a className="text-warning" title="Edit"><span className="fa fa-pencil" /></a>
                            <a className="text-danger" title="Remove"><span className="fa fa-trash" /></a>
                            <AddFormComponent parentId={component.key} />
                          </div>
                      }                  
                      <NestedLayoutComponent componentKey={component.key} />
                    </div>               
                  </div>                 
                )
              }
              else if (component.type === "paragraph") {
                return (
                  <div className="alert alert-success text-font-bold" key={component.key}>
                    {
                      <FormComponent component={component} editMode={props.editMode} />
                    } 
                  </div>
                )
              }
              else {
                return (
                  <div key={component.key}>
                    {
                      <FormComponent component={component} editMode={props.editMode} />
                    }   
                  </div>                
                )
              }        
            })
          }  
        </GridLayout> 
      </div>  
    )
    
    export default SizeMe()(LayoutComponent)

    编辑2:

    AddFormComponent.js - Component

    import React from 'react'
    import AddFormComponentDetails from './AddFormComponentDetails'
    
    import Perf from 'react-addons-perf'; // ES6
    
    class AddFormComponent extends React.Component {
    
      constructor(props) {
        super(props);
        this.localOpenModal = this.localOpenModal.bind(this);
      }
    
      localOpenModal() {
        console.log("----STARTING PERFORMANCE MONITOR-----")
        Perf.start()
        this.props.handleOpenModal();
      }
    
      componentDidUpdate() {
        console.log("-----PERFORMANCE MONITOR STOPPING------")
        Perf.stop()
        console.log("-----PRINT INCLUSIVE------")
        Perf.printInclusive()
        console.log("-----PRINT WASTEED------")
        Perf.printWasted()
      }
    
      render() {
        return (
      <span>
        <a onTouchTap={this.localOpenModal} className="text-success" title="Add Component">
          <span className="fa fa-plus" />
        </a>
    
        <Modal isOpen={this.props.modalOpen} size={"lgr"} toggle={this.props.handleCloseModal}>
          <ModalHeader toggle={this.props.handleCloseModal}>Add Component</ModalHeader>
          <ModalBody>
            ...Removed For Breviety
          </ModalBody>
          <ModalFooter>
            ...Removed For Breviety       
          </ModalFooter>
        </Modal>
      </span>
    )
      }
    } 
    
    export default AddFormComponent

    AddFormComponent.js - Container

    import { connect } from 'react-redux'
    import { 
      handleOpenModal,
      handleCloseModal,
      handleGoBack,
      handleComponentPropertyChange,
      handleComponentNameChange,
      handleComponentTypeChange,
      handleSubmit
    } from '../actions/addFormComponentActions'
    import AddFormComponent from '../components/AddFormComponent'
    
    const mapStateToProps = (state) => ({
      steps: [
        { icon: 'superpowers', title: 'Select Component', description: 'Select the Component you wish to add', active: state.addComponent.currentStep == 1 },
        { icon: 'info circle', title: 'Enter Details', description: 'Enter details to customize component', active: state.addComponent.currentStep == 2 },
        { icon: 'check', title: 'Add Component', description: 'Add component to form' }
      ],
      currentStep: state.addComponent.currentStep,
      modalOpen: state.addComponent.modalOpen,
      component: state.addComponent.component,
      errors: state.addComponent.errors,
      componentType: state.addComponent.componentType
    })
    
    export default connect(
      mapStateToProps, 
      {
        handleOpenModal,
        handleCloseModal,
        handleGoBack,
        handleComponentPropertyChange,
        handleComponentNameChange,
        handleComponentTypeChange,
        handleSubmit
      }
    )(AddFormComponent)

    addFormComponentReducer.js

    import _ from 'lodash'
    import {
      ADD_FORM_COMPONENT_TOGGLE_MODAL,
      ADD_FORM_COMPONENT_CLOSE_MODAL,
      ADD_FORM_COMPONENT_GO_BACK,
      ADD_FORM_COMPONENT_SUBMIT,
      ADD_FORM_COMPONENT_PROPERTY_CHANGE,
      ADD_FORM_COMPONENT_PROPERTY_ERROR,
      ADD_FORM_COMPONENT_KEY_ERROR,
      ADD_FORM_COMPONENT_NAME_CHANGE,
      ADD_FORM_COMPONENT_NAME_ERROR,
      ADD_FORM_COMPONENT_TYPE_CHANGE,
      ADD_FORM_COMPONENT_TYPE_ERROR
    } from '../actions/addFormComponentActions'
    
    let initialState = {
      currentStep: 1,
      modalOpen: false,
      component: {
        key: '',
        label: '',
        headingText: '',
        text: ''
      },
      errors: { 
        key: {
          hasError: false,
          msg: ''
        },
        label: {
          hasError: false,
          msg: ''
        },
        text: {
          hasError: false,
          msg: ''
        }
      }
    }
    
    function addFormComponentReducer(state = initialState, action) {
      switch (action.type) {
        case ADD_FORM_COMPONENT_TOGGLE_MODAL:
          return {
            ...state,
            modalOpen: action.payload.isOpen,
            currentStep: 1
          }
        case ADD_FORM_COMPONENT_CLOSE_MODAL:
          return initialState;
        case ADD_FORM_COMPONENT_GO_BACK:
          return {
            ...state,
            currentStep: 1
          }
        case ADD_FORM_COMPONENT_SUBMIT:
          return initialState;
        case ADD_FORM_COMPONENT_PROPERTY_CHANGE:
          return {
            ...state,
            component: {
              ...state.component,
              [action.payload.key]: action.payload.value
            }        
          }
        case ADD_FORM_COMPONENT_PROPERTY_ERROR:
          return {
            ...state,
            errors: {
              ...state.errors,
              [action.payload.key]: {
                hasError: action.payload.hasError,
                msg: action.payload.msg
              }
            }        
          }
        case ADD_FORM_COMPONENT_TYPE_CHANGE:
          return {
            ...state,
            componentType: action.payload.componentType,
            currentStep: 2
          }
        default:
          return state
      }
    }
    
    export default addFormComponentReducer

    index.js - 结合减速器

    import { combineReducers } from 'redux'
    
    //import FormEngine reducers
    import formReducer from './formReducer'
    //import addFormComponentReducer from './addFormComponentReducer'
    import componentEditorReducer from './componentEditorReducer'
    
    const rootFormEngineReducer = combineReducers({
      form: formReducer,
      //addComponent: addFormComponentReducer,
      componentEditor: componentEditorReducer
    })
    
    export default rootFormEngineReducer

    rootReducer.js

    import { combineReducers } from 'redux'
    
    //import reducers
    import rootCoreLayoutReducer from '../features/CoreLayout/reducers'
    import rootFormEngineReducer from '../features/FormEngine/reducers'
    import addComponentReducer from '../features/FormEngine/reducers/addFormComponentReducer'
    
    const rootReducer = combineReducers({
      coreLayout: rootCoreLayoutReducer,
      formEngine: rootFormEngineReducer,
      addComponent: addComponentReducer
    })
    
    export default rootReducer

1 个答案:

答案 0 :(得分:2)

如果您使用纯组件,则必须手动处理任何性能优化(使用shouldComponentUpdate)。由于您使用的是redux,它可以为您处理。但是你必须将它“连接”到redux商店。

如果您选择使用redux connect,请确保模式可见性与您的其他属性无关:

modalOpen嵌套在formEngine中。当它改变任何其他侦听formEngine的东西时会重新渲染