MVC
MVC в реальном проекте
React data/event flow
Что у нас есть?
var CommentBox = React.createClass({
loadCommentsFromServer: function() { ... },
handleCommentSubmit: function(comment) { ... }.
getInitialState: function() { return {data: []}; },
componentDidMount: function() { ... },
render: function() { ... }
});
var Comment = React.createClass({
render: function() { ... }
});
var CommentList = React.createClass({
render: function() { ... }
});
var CommentForm = React.createClass({
handleSubmit: function(e) {},
render: function() { ... }
});
Или вот
Zoom in
var CommentBox = React.createClass({
loadCommentsFromServer: function() {
$.ajax({
url: this.props.url,
success: function(data) { this.setState({data: data}); },
...
});
},
handleCommentSubmit: function(comment) {
var comments = this.state.data;
comments.push(comment);
this.setState({data: comments}, function() {
$.ajax({
url: this.props.url,
data: comment,
...
}
});
});
},
componentDidMount: function() {
this.loadCommentsFromServer();
...
}
});
Single source of truth
function getStateFromStore() {
return {
data: CommentStore.getAll()
};
}
var CommentBox = React.createClass({
handleCommentSubmit: function(comment) {
Actions.create(comment);
},
getInitialState: function() {
return getStateFromStore();
},
componentDidMount: function() { // Слушаем события от стора
CommentStore.addChangeListener(this._onChange);
},
componentWillUnmount: function() { // Перестаем слушать события
CommentStore.removeChangeListener(this._onChange);
},
_onChange: function() {
this.setState(getStateFromStore());
}
});
Action?
Let's see action
var Actions = {
recieveComments: function(comments) {
AppDispatcher.dispatch({
actionType: 'RECEIVE_RAW_COMMENTS',
rawComments: comments
});
},
create: function(data) {
AppDispatcher.dispatch({
actionType: 'COMMENT_CREATE',
data: data
});
WebUtils.createComment(data);
}
}
Dispatcher
var AppDispatcher = require('flux').Dispatcher;
Где обработка actions?
var CommentStore = assign({}, EventEmitter.prototype, {
getAll: function() { return _comments; },
emitChange: function() { this.emit(CHANGE_EVENT); },
addChangeListener: function(callback) { ... },
removeChangeListener: function(callback) { .. }
});
AppDispatcher.register(function(action) {
var author, text;
switch(action.actionType) {
case 'COMMENT_CREATE':
_create(action.data);
CommentStore.emitChange();
break;
case 'RECEIVE_RAW_COMMENTS':
_addComments(action.rawComments);
CommentStore.emitChange();
break;
default:
}
});
Где мои данные, чувак?
var WebApi = {
fetchComments: function() {
$.ajax({
url: COMMENTS_URL,
success: function(data) {
Actions.recieveComments(data);
}.bind(this)
});
},
createComment: function(comment) {
$.ajax({
url: COMMENTS_URL,
type: 'POST',
data: comment,
success: function(data) {
Actions.recieveComments(data);
}.bind(this)
});
}
}
Когда синхронизироваться с сервером?
// app.js
WebUtils.fetchComments();
// Poll new comments
setInterval(WebUtils.fetchComments, POLL_INTERVAL);
React.render(
<CommentBox/>,
document.getElementById('content')
);
var Actions = {
...
create: function(data) {
AppDispatcher.dispatch({
actionType: 'COMMENT_CREATE',
data: data
});
WebUtils.createComment(data);
}
}
До и после
public/scripts public/scripts
`-- example.js |-- actions
| |-- ServerActionsCreators.js
| `-- ViewActionsCreators.js
|-- app.js
|-- components
| |-- Comment.js
| |-- CommentBox.js
| |-- CommentForm.js
| `-- CommentList.js
|-- constants
| `-- CommentConstants.js
|-- dispatcher
| `-- CommentDispatcher.js
|-- stores
| `-- CommentStore.js
`-- utils
`-- WebApi.js
ОК. А что получат роботы?
> curl http://localhost:3000
<html>
<head>
<title>Hello React</title>
<link rel="stylesheet" href="css/base.css" />
<script src="react.js"></script>
...
</head>
<body>
<div id="content"></div>
...
</body>
</html>
Das' kinda sad
Isomorphic JavaScript
base.html
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<link rel="stylesheet" href="css/base.css" />
</head>
<body>
<div id="content">
<%= body %>
</div>
<script>
// var App = {
// CommentStore: { comments: [...] }
// }
<%= setState %>
</script>
<script src="bundle.js"></script>
</body>
</html>
app.js
WebUtils.fetchComments();
document.addEventListener('DOMNodeInserted', function (event) {
console.log(event);
});
CommentStore.init(window.App);
React.render(
<CommentBox/>,
document.getElementById('content')
);
commentstore.js
...
var CommentStore = {
init: function(state) {
if(state && state['CommentStore']) {
Actions.recieveComments(state['CommentStore']);
}
},
...
};
server.js
var React = require('react');
var CommentBox = React.createFactory(require('./public/scripts/components/CommentBox'));
var CommentStore = require('./public/scripts/stores/CommentStore');
var template = fs.readFileSync(path.join(__dirname, 'public/base.html'), 'utf8');
app.get('/', function(req, res) {
var data = {};
var comments = JSON.parse(fs.readFileSync('_comments.json', 'utf8'));
var state = {
'CommentStore': comments
}
CommentStore.init(state);
data.body = React.renderToString(CommentBox());
data.setState = 'window.App=' + serialize(state) + ';'
res.send(_.template(template)(data));
});
One more time
> curl http://localhost:3000
<!DOCTYPE html>
<html>
<head>
<title>Hello React</title>
<link rel="stylesheet" href="css/base.css" />
</head>
<body>
<div id="content">
<div class="commentBox" data-reactid=".1x6vuuopdds"
data-react-checksum="143514520"><h1 data-reactid=".1x6vuuopdds.0">Comments</h1>
<div class="commentList" data-reactid=".1x6vuuopdds.1">
<div class="comment" data-reactid=".1x6vuuopdds.1.$0">
<h2 class="commentAuthor" data-reactid=".1x6vuuopdds.1.$0.0">Pete Hunt</h2>
<span data-reactid=".1x6vuuopdds.1.$0.1">Hey there!</span></div></div><form class="commentForm" data-reactid=".1x6vuuopdds.2"><input type="text" placeholder="Your name" data-reactid=".1x6vuuopdds.2.0"><input type="text" placeholder="Say something..." data-reactid=".1x6vuuopdds.2.1"><input type="submit" value="Post" data-reactid=".1x6vuuopdds.2.2"></form></div>
</div>
<script>
window.App={"CommentStore":[{"author":"Pete Hunt","text":"Hey there!"}]};
</script>
<script src="bundle.js"></script>
</body>
</html>