1.shouldComponentUpdate
一个组件更新时,无论是设置了新的props/调用了setState方法/调用forceUpdate方法,React都会调用该组件所有子组件的render方法。在组件树深度嵌套或render方法十分复杂的页面上这可能会带来延迟。
组件的render方法有时候会在不必要的情况下被调用。如:在组件渲染过程中没有使用props或state值,或组件的props或state并没有在父组件重新渲染时发生改变时,重新渲染这个组件会得到和已存在的虚拟DOM结构一模一样的结构,这样的设计过程是没有必要的。
React提供的组件生命周期方法:shouldComponentUpdate可以帮助React正确地判断是否需要调用指定组件的render方法。
shouldComponentUpdate返回false即可以不调用组件的渲染方法,并使用之前渲染好的虚拟DOM;返回true则是让React调用组件的渲染方法并计算出新的虚拟DOM。默认返回true,因此组件总是会调用render方法。
在组件首次渲染时,shouldComponentUpdate方法不会被调用。
shouldComponentUpdate方法接收两个参数,即新的props和新的state,以帮助你决定是否重新渲染:
var SurveyEditor = React.createClass({
shouldComponentUpdate:function (nextProps,nextState) {
return nextProps.id !== this.props.id;
}
});
对于给定同样的props和state总是渲染出同样结果的组件,我们可以添加React.addons.PureRenderMixin插件来处理shouldComponentUpdate。
这个插件会重写shouldComponentUpdate方法,并在该方法内对新老props及state进行对比,如果发现它们完全一致则返回false,如上面的例子。
var EditEssayQuestion = React.createClass({
mixin: [React.addons.PureRenderMixin],
propTypes: {
key: React.PropTypes.number.isrequired,122);font-weight:bold;">onChange: React.PropTypes.func.isrequired,122);font-weight:bold;">onRemove: question: React.PropTypes.object.isrequired
},
render: function () {
description = this.props.question.description || "";
return (
<EditQuestion type='essay' onRemove={this.handleRemove}>
label>Description</input ='text' className='description' value={description} onChangehandleChange} />
EditQuestion>
);
},67);">handleChange: function (ev) {
question = merge(question,{ description: ev.target.value });
onChange(key, question);
},67);">handleRemove: onRemove(key);
}
});
如果props或state结构较深或较复杂,对比的过程会比较缓慢。为减少这种情况带来的问题,可以考虑使用不可变的数据结构,比如:Immutable.js,或使用不可变性辅助插件。
2.不可变性辅助插件
在需要比较对象以确认是否更新时,使用不可变的数据结构能让shouldComponentUpdate方法变得更加简单。
可以使用React.addons.update来确保组件的不可变性。React.addons.update接受一个数据结构和一个配置对象。可以在配置对象中传入$slice、$push、$unshift、$set、$merge和$apply。
React = require('react/addons');
Divider = require('./divider');
DraggableQuestions = require('./draggable_questions');
SurveyForm = require('./survey_form');
EditYesNoQuestion = require('./questions/edit_yes_no_question');
EditMultipleChoiceQuestion = require('./questions/edit_multiple_choice_question');
EditEssayQuestion = require('./questions/edit_essay_question');
SurveyActions = require("../flux/SurveyActions");
update = React.addons.update;
ReactCSSTransitionGroup = React.addons.CSSTransitionGroup;
SUPPORTED_QUESTIONS = {
yes_no: EditYesNoQuestion,122);font-weight:bold;">multiple_choice: EditMultipleChoiceQuestion,122);font-weight:bold;">essay: EditEssayQuestion
};
SurveyEditor = React.createClass({
getInitialState: return {
dropZoneEntered: false,122);font-weight:bold;">title: '',122);font-weight:bold;">introduction: questions: []
};
},131);">questions = state.questions.map(function (q,i) {
return SUPPORTED_QUESTIONS[q.type]({
key: i,122);font-weight:bold;">onChange: handleQuestionChange,122);font-weight:bold;">onRemove: handleQuestionRemove,122);font-weight:bold;"> question: q
});
}.bind(this));
dropZoneEntered = '';
if (dropZoneEntered) {
'drag-enter';
}
div ='survey-editor'='row' aside ='sidebar col-md-3'h2>ModulesDraggableQuestions aside='survey-canvas col-md-9'SurveyForm
titlestate.title}
introductionintroduction}
handleFormChange}
Divider>QuestionsReactCSSTransitionGroup transitionName='question'>
{questions}
ReactCSSTransitionGroupdiv
'drop-zone well well-drop-zone ' + dropZoneEntered}
onDragOverhandleDragOver}
onDragEnterhandleDragEnter}
onDragLeavehandleDragLeave}
onDrophandleDrop}
>
Drag and drop a module from the left
div='actions'button ="btn btn-lg btn-primary btn-save" onClickhandleSaveClicked}>Savebutton>
);
},67);">handleFormChange: function (formData) {
this.setState(formData);
},67);">handleDragOver: function (ev) {
// This allows handleDropZoneDrop to be called
// https://code.google.com/p/chromium/issues/detail?id=168387
ev.preventDefault();
},67);">handleDragEnter: this.setState({true});
},67);">handleDragLeave: false});
},67);">handleDrop: questionType = ev.dataTransfer.getData('questionType');
questions = update(questions,{
$push: [{ type: questionType }]
});
this.setState({
questions: questions,128);font-weight:bold;">false
});
},67);">handleQuestionChange: function (key,newQuestion) {
$splice: [[key, 1,newQuestion]]
});
this.setState({ questions });
},67);">handleQuestionRemove: function (key) {
1]]
});
handleSaveClicked: function (ev) {
SurveyActions.save({
state.title,122);font-weight:bold;">introduction,122);font-weight:bold;">questions: questions
});
}
});
3.深入调查拖慢应用的部分
React.addons.Perf插件能找到添加shouldComponentUpdate的最佳位置。
首先在Chrome控制台中运行React.addons.Perf.start();该命令会启动采集快照。
然后在页面操作后运行React.addons.Perf.stop();
最后再在控制台运行React.addons.Perf.printWaster();会输出列表,列表中包含组件和组件耗费时间。对于没有改变props和state的组件可以设置shouldComponentUpdate:function(){return false;}
4.键(key)
列表中使用key属性。作用:给React提供一种除组件类之外的识别子组件的最小变化。
项目地址:https://github.com/backstopmedia/bleeding-edge-sample-app
参考书:《React引领未来的用户界面开发框架》