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>
					

Ссылки