React-Redux - 如何更新一段状态而不会导致无关组件不必要的重新渲染
我很努力地以简明的方式提出这个问题。我的应用程序遇到一些主要的性能问题。我已经安装了Perf附加工具以进行反应,并且可以查看问题出在哪里,但是我不确定解决问题的最佳方法。React-Redux - 如何更新一段状态而不会导致无关组件不必要的重新渲染
我认为这可能与ReSelect有关......但需要一些指导从何处开始。我有一个呈现其他组件的组件。这使用size-me(计算浏览窗口的大小)和react-grid-layout(布局每个组件并允许更改其定位)。这是资源密集型的,所以我不能不必要地发生这种情况。
用户可以点击一个按钮来打开一个模式窗口(添加或编辑在网格中渲染的组件)。
问题:当模式窗口打开时,底层组件重新渲染,导致size-me和react-grid-layout重新渲染,从而导致模态“猛地”打开!
大小,我和应对电网布局的东西从状态树的formEngine.form部分渲染状态,但是当对树的formEngine.addComponent部分进行状态更新时,它将被重新渲染
正如你所看到的,也有一些浪费呈现发生的事情,这只会逐步增长基于用户决定添加到表单嵌套布局组件的数量...
所以尝试防止这个问题变得太令人费解,让我先问:
- 如何防止重新渲染底层页面当我打开模式?
- 为什么当fromEngine.addComponent被修改时,正在监视formEngine.form的组件被触发以重新呈现?
谢谢。
编辑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 - 元器件
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 - 集装箱
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 - 结合热度的CER
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
如果使用纯组分的任何性能优化具有(使用shouldComponentUpdate)手动处理。既然你使用的是REDX,它可以为你处理。但是你必须将它连接到redux商店。
如果您选择使用Redux的连接保证了模态能见度不相关的其他属性特别是在你的情况:
modalOpen嵌套在formEngine。当它改变任何听到formEngine的东西时,就会退回
谢谢你的回答。我测试了这一点。我移动了modalOpen在formEngine之外,但是我仍然在Perf日志中获得了相同的结果(它仍然重新渲染了SizeMe/react-grid-layout),我在上面添加了这个组件的代码,这可能与SizeMe检测有关宽度的变化?即使没有向浏览器添加滚动条,它仍然会重新渲染,因此可能不会。 –
一眼看来,您似乎正在通过“modalOpen”作为道具。由于父组件负责传递此值,因此modalOpen中的更改将导致父级重新呈现,因为它必须向下传递道具。无论如何,这是我的猜测。尝试将你的模态直接连接到redux商店 –
我还在传递连接的mapStateToProps方法中硬编码属性。我玩了一下,似乎也造成了问题。不完全知道为什么,因为状态没有改变,但是当我从其中删除道具并将它们作为缩减器中的初始状态时,布局组件在打开模态时停止了重新渲染。 –
什么是组件的树结构?模态组件是size-me和react-grid-layout的兄弟姐妹吗? – rauliyohmc
是的,我相信这是一个兄弟...不太清楚,如果这就是你要求的...但我添加了一些代码。所以有AddFormComponent,它是打开的Modal。它显示在页面的顶部(在任何size-me/react-grid-layout内容发挥作用之前,但它也显示在每个“LayoutComponent”中,因为LayoutComponent可以包含嵌套的Components/LayoutComponents。 –
你是否*地使用'PureComponents'和/或'shouldComponentUpdate'?你是否只传递每个组件所需的道具(即没有通用的状态传播)? – monners