Skip to content

Commit

Permalink
React - Improve performance by 6x
Browse files Browse the repository at this point in the history
 - 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
  • Loading branch information
vjeux authored and petehunt committed Oct 29, 2013
1 parent 145874b commit b25f5ca
Show file tree
Hide file tree
Showing 4 changed files with 55 additions and 31 deletions.
2 changes: 1 addition & 1 deletion labs/architecture-examples/react/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand Down
46 changes: 27 additions & 19 deletions labs/architecture-examples/react/js/app.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 = '';
}

Expand All @@ -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) {
Expand All @@ -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 () {
Expand All @@ -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) {
Expand All @@ -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)}
Expand All @@ -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 =
Expand Down
25 changes: 14 additions & 11 deletions labs/architecture-examples/react/js/todoItem.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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) {
Expand All @@ -41,8 +43,6 @@
this.props.onCancel();
} else if (event.keyCode === ENTER_KEY) {
this.handleSubmit();
} else {
this.setState({editText: event.target.value});
}
},

Expand All @@ -54,33 +54,36 @@
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}
/>
<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}
Expand Down
13 changes: 13 additions & 0 deletions labs/architecture-examples/react/js/utils.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down

0 comments on commit b25f5ca

Please sign in to comment.