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 @@
<div id="benchmark"></div>
<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/director/build/director.min.js"></script>
......
......@@ -43,17 +43,15 @@
}
var val = this.refs.newField.getDOMNode().value.trim();
var todos;
var newTodo;
if (val) {
todos = this.state.todos;
newTodo = {
id: Utils.uuid(),
title: val,
completed: false
};
this.setState({todos: todos.concat([newTodo])});
this.setState({todos: this.state.todos.concat([newTodo])});
this.refs.newField.getDOMNode().value = '';
}
......@@ -63,16 +61,22 @@
toggleAll: function (event) {
var checked = event.target.checked;
this.state.todos.forEach(function (todo) {
todo.completed = checked;
var newTodos = this.state.todos.map(function (todo) {
return Utils.extend({}, todo, {completed: checked});
});
this.setState({todos: this.state.todos});
this.setState({todos: newTodos});
},
toggle: function (todo) {
todo.completed = !todo.completed;
this.setState({todos: this.state.todos});
var newTodos = this.state.todos.map(function (t) {
if (t !== todo) {
return t;
}
return Utils.extend({}, t, {completed: !todo.completed});
});
this.setState({todos: newTodos});
},
destroy: function (todo) {
......@@ -92,8 +96,14 @@
},
save: function (todo, text) {
todo.title = text;
this.setState({todos: this.state.todos, editing: null});
var newTodos = this.state.todos.map(function (t) {
if (t !== todo) {
return t;
}
return Utils.extend({}, t, {title: text});
});
this.setState({todos: newTodos, editing: null});
},
cancel: function () {
......@@ -115,9 +125,6 @@
render: function () {
var footer = null;
var main = null;
var todoItems = {};
var activeTodoCount;
var completedCount;
var shownTodos = this.state.todos.filter(function (todo) {
switch (this.state.nowShowing) {
......@@ -128,11 +135,12 @@
default:
return true;
}
}.bind(this));
}, this);
shownTodos.forEach(function (todo) {
todoItems[todo.id] = (
var todoItems = shownTodos.map(function (todo) {
return (
<TodoItem
key={todo.id}
todo={todo}
onToggle={this.toggle.bind(this, todo)}
onDestroy={this.destroy.bind(this, todo)}
......@@ -142,13 +150,13 @@
onCancel={this.cancel}
/>
);
}.bind(this));
}, this);
activeTodoCount = this.state.todos.filter(function (todo) {
var activeTodoCount = this.state.todos.filter(function (todo) {
return !todo.completed;
}).length;
completedCount = this.state.todos.length - activeTodoCount;
var completedCount = this.state.todos.length - activeTodoCount;
if (activeTodoCount || completedCount) {
footer =
......
......@@ -23,6 +23,7 @@
}
return false;
},
handleEdit: function () {
// react optimizes renders by batching them. This means you can't call
// parent's `onEdit` (which in this case triggeres a re-render), and
......@@ -33,6 +34,7 @@
node.focus();
node.setSelectionRange(node.value.length, node.value.length);
}.bind(this));
this.setState({editText: this.props.todo.title});
},
handleKeyDown: function (event) {
......@@ -41,8 +43,6 @@
this.props.onCancel();
} else if (event.keyCode === ENTER_KEY) {
this.handleSubmit();
} else {
this.setState({editText: event.target.value});
}
},
......@@ -54,21 +54,24 @@
return {editText: this.props.todo.title};
},
componentWillReceiveProps: function (nextProps) {
if (nextProps.todo.title !== this.props.todo.title) {
this.setState(this.getInitialState());
}
shouldComponentUpdate: function (nextProps, nextState) {
return (
nextProps.todo.id !== this.props.todo.id ||
nextProps.todo !== this.props.todo ||
nextProps.editing !== this.props.editing ||
nextState.editText !== this.state.editText
);
},
render: function () {
return (
<li class={Utils.stringifyObjKeys({
<li className={Utils.stringifyObjKeys({
completed: this.props.todo.completed,
editing: this.props.editing
})}>
<div class="view">
<div className="view">
<input
class="toggle"
className="toggle"
type="checkbox"
checked={this.props.todo.completed ? 'checked' : null}
onChange={this.props.onToggle}
......@@ -76,11 +79,11 @@
<label onDoubleClick={this.handleEdit}>
{this.props.todo.title}
</label>
<button class='destroy' onClick={this.props.onDestroy} />
<button className="destroy" onClick={this.props.onDestroy} />
</div>
<input
ref="editField"
class="edit"
className="edit"
value={this.state.editText}
onBlur={this.handleSubmit}
onChange={this.handleChange}
......
......@@ -32,6 +32,19 @@
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) {
var s = '';
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