Commit d456aeb1 authored by Vjeux's avatar Vjeux Committed by petehunt

React - Improve performance by 6x

 - 2x: The unminified version contains invariants that shouldn't be used in production and slow it down.

 - 3x: Adding a shouldComponentUpdate override in order to short-cut re-rendering the component when props and state didn't change. In order to do that, we need to avoid doing mutations by doing a shallow copy and only modifying what changed

 - When React 0.5.0 will be released, it will give another 1.5x.

Other miscellaneous changes:

 - Update class="..." to className="..." to work in 0.5.0 (still works in 0.4.1)
 - Use prop="..." instead of prop='...'
 - Use filter(f, this) instead of filter(f.bind(this)) to prevent a function allocaton
parent caac5a04
...@@ -12,7 +12,7 @@ ...@@ -12,7 +12,7 @@
<div id="benchmark"></div> <div id="benchmark"></div>
<script src="bower_components/todomvc-common/base.js"></script> <script src="bower_components/todomvc-common/base.js"></script>
<script src="bower_components/react/react.js"></script> <script src="bower_components/react/react.min.js"></script>
<script src="bower_components/react/JSXTransformer.js"></script> <script src="bower_components/react/JSXTransformer.js"></script>
<script src="bower_components/director/build/director.min.js"></script> <script src="bower_components/director/build/director.min.js"></script>
......
...@@ -43,17 +43,15 @@ ...@@ -43,17 +43,15 @@
} }
var val = this.refs.newField.getDOMNode().value.trim(); var val = this.refs.newField.getDOMNode().value.trim();
var todos;
var newTodo; var newTodo;
if (val) { if (val) {
todos = this.state.todos;
newTodo = { newTodo = {
id: Utils.uuid(), id: Utils.uuid(),
title: val, title: val,
completed: false completed: false
}; };
this.setState({todos: todos.concat([newTodo])}); this.setState({todos: this.state.todos.concat([newTodo])});
this.refs.newField.getDOMNode().value = ''; this.refs.newField.getDOMNode().value = '';
} }
...@@ -63,16 +61,22 @@ ...@@ -63,16 +61,22 @@
toggleAll: function (event) { toggleAll: function (event) {
var checked = event.target.checked; var checked = event.target.checked;
this.state.todos.forEach(function (todo) { var newTodos = this.state.todos.map(function (todo) {
todo.completed = checked; return Utils.extend({}, todo, {completed: checked});
}); });
this.setState({todos: this.state.todos}); this.setState({todos: newTodos});
}, },
toggle: function (todo) { toggle: function (todo) {
todo.completed = !todo.completed; var newTodos = this.state.todos.map(function (t) {
this.setState({todos: this.state.todos}); if (t !== todo) {
return t;
}
return Utils.extend({}, t, {completed: !todo.completed});
});
this.setState({todos: newTodos});
}, },
destroy: function (todo) { destroy: function (todo) {
...@@ -92,8 +96,14 @@ ...@@ -92,8 +96,14 @@
}, },
save: function (todo, text) { save: function (todo, text) {
todo.title = text; var newTodos = this.state.todos.map(function (t) {
this.setState({todos: this.state.todos, editing: null}); if (t !== todo) {
return t;
}
return Utils.extend({}, t, {title: text});
});
this.setState({todos: newTodos, editing: null});
}, },
cancel: function () { cancel: function () {
...@@ -115,9 +125,6 @@ ...@@ -115,9 +125,6 @@
render: function () { render: function () {
var footer = null; var footer = null;
var main = null; var main = null;
var todoItems = {};
var activeTodoCount;
var completedCount;
var shownTodos = this.state.todos.filter(function (todo) { var shownTodos = this.state.todos.filter(function (todo) {
switch (this.state.nowShowing) { switch (this.state.nowShowing) {
...@@ -128,11 +135,12 @@ ...@@ -128,11 +135,12 @@
default: default:
return true; return true;
} }
}.bind(this)); }, this);
shownTodos.forEach(function (todo) { var todoItems = shownTodos.map(function (todo) {
todoItems[todo.id] = ( return (
<TodoItem <TodoItem
key={todo.id}
todo={todo} todo={todo}
onToggle={this.toggle.bind(this, todo)} onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)} onDestroy={this.destroy.bind(this, todo)}
...@@ -142,13 +150,13 @@ ...@@ -142,13 +150,13 @@
onCancel={this.cancel} onCancel={this.cancel}
/> />
); );
}.bind(this)); }, this);
activeTodoCount = this.state.todos.filter(function (todo) { var activeTodoCount = this.state.todos.filter(function (todo) {
return !todo.completed; return !todo.completed;
}).length; }).length;
completedCount = this.state.todos.length - activeTodoCount; var completedCount = this.state.todos.length - activeTodoCount;
if (activeTodoCount || completedCount) { if (activeTodoCount || completedCount) {
footer = footer =
......
...@@ -23,6 +23,7 @@ ...@@ -23,6 +23,7 @@
} }
return false; return false;
}, },
handleEdit: function () { handleEdit: function () {
// react optimizes renders by batching them. This means you can't call // react optimizes renders by batching them. This means you can't call
// parent's `onEdit` (which in this case triggeres a re-render), and // parent's `onEdit` (which in this case triggeres a re-render), and
...@@ -33,6 +34,7 @@ ...@@ -33,6 +34,7 @@
node.focus(); node.focus();
node.setSelectionRange(node.value.length, node.value.length); node.setSelectionRange(node.value.length, node.value.length);
}.bind(this)); }.bind(this));
this.setState({editText: this.props.todo.title});
}, },
handleKeyDown: function (event) { handleKeyDown: function (event) {
...@@ -41,8 +43,6 @@ ...@@ -41,8 +43,6 @@
this.props.onCancel(); this.props.onCancel();
} else if (event.keyCode === ENTER_KEY) { } else if (event.keyCode === ENTER_KEY) {
this.handleSubmit(); this.handleSubmit();
} else {
this.setState({editText: event.target.value});
} }
}, },
...@@ -54,21 +54,24 @@ ...@@ -54,21 +54,24 @@
return {editText: this.props.todo.title}; return {editText: this.props.todo.title};
}, },
componentWillReceiveProps: function (nextProps) { shouldComponentUpdate: function (nextProps, nextState) {
if (nextProps.todo.title !== this.props.todo.title) { return (
this.setState(this.getInitialState()); nextProps.todo.id !== this.props.todo.id ||
} nextProps.todo !== this.props.todo ||
nextProps.editing !== this.props.editing ||
nextState.editText !== this.state.editText
);
}, },
render: function () { render: function () {
return ( return (
<li class={Utils.stringifyObjKeys({ <li className={Utils.stringifyObjKeys({
completed: this.props.todo.completed, completed: this.props.todo.completed,
editing: this.props.editing editing: this.props.editing
})}> })}>
<div class="view"> <div className="view">
<input <input
class="toggle" className="toggle"
type="checkbox" type="checkbox"
checked={this.props.todo.completed ? 'checked' : null} checked={this.props.todo.completed ? 'checked' : null}
onChange={this.props.onToggle} onChange={this.props.onToggle}
...@@ -76,11 +79,11 @@ ...@@ -76,11 +79,11 @@
<label onDoubleClick={this.handleEdit}> <label onDoubleClick={this.handleEdit}>
{this.props.todo.title} {this.props.todo.title}
</label> </label>
<button class='destroy' onClick={this.props.onDestroy} /> <button className="destroy" onClick={this.props.onDestroy} />
</div> </div>
<input <input
ref="editField" ref="editField"
class="edit" className="edit"
value={this.state.editText} value={this.state.editText}
onBlur={this.handleSubmit} onBlur={this.handleSubmit}
onChange={this.handleChange} onChange={this.handleChange}
......
...@@ -32,6 +32,19 @@ ...@@ -32,6 +32,19 @@
return (store && JSON.parse(store)) || []; return (store && JSON.parse(store)) || [];
}, },
extend: function () {
var newObj = {};
for (var i = 0; i < arguments.length; i++) {
var obj = arguments[i];
for (var key in obj) {
if (obj.hasOwnProperty(key)) {
newObj[key] = obj[key];
}
}
}
return newObj;
},
stringifyObjKeys: function (obj) { stringifyObjKeys: function (obj) {
var s = ''; var s = '';
var key; var key;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment