LoginSignup
1
1

More than 5 years have passed since last update.

flux demo todomvc

Posted at

Kobito.Y2j11m.png

look

index.html文件只有用到js/bundle.js 文件, 然后bundle.js哪里来, 看看package.json,

  "scripts": {
    "start": "watchify -o js/bundle.js -v -d js/app.js",
    "build": "browserify . -t [envify --NODE_ENV production] | uglifyjs -cm > js/bundle.min.js",
    "test": "jest"
  }

app.js 作为入口, 然后监视, 一旦发生改变就回编译到 bundle.js.

然后接着看, app.js 内容:

var React = require('react');

var TodoApp = require('./components/TodoApp.react'); // here

React.render(
  <TodoApp />,
  document.getElementById('todoapp')
);

一个TodoApp 搞定一切. 至于它何来头这就要开篇讲了.

创建一个 dispatcher

创建一个 dispatcher, 下面是一个简单例子,使用了 javascript 的 promises


var Promise = require('es6-promise').Promise;
var assign = require('object-assign');

var _callbacks = [];
var _promises = [];

var Dispatcher = function() {};
Dispatcher.prototype = assign({}, Dispatcher.prototype, {

  /**
   * Register a Store's callback so that it may be invoked by an action.
   * @param {function} callback The callback to be registered.
   * @return {number} The index of the callback within the _callbacks array.
   */
  register: function(callback) {
    _callbacks.push(callback);
    return _callbacks.length - 1; // index
  },

  /**
   * dispatch
   * @param  {object} payload The data from the action.
   */
  dispatch: function(payload) {
    // First create array of promises for callbacks to reference.
    var resolves = [];
    var rejects = [];
    _promises = _callbacks.map(function(_, i) {
      return new Promise(function(resolve, reject) {
        resolves[i] = resolve;
        rejects[i] = reject;
      });
    });
    // Dispatch to callbacks and resolve/reject promises.
    _callbacks.forEach(function(callback, i) {
      // Callback can return an obj, to resolve, or a promise, to chain.
      // See waitFor() for why this might be useful.
      Promise.resolve(callback(payload)).then(function() {
        resolves[i](payload);
      }, function() {
        rejects[i](new Error('Dispatcher callback unsuccessful'));
      });
    });
    _promises = [];
  }
});

module.exports = Dispatcher;

这个dispatch 只公开了2个方法register()dispatch(),我们将会使用register() 来给store 注册回调函数, 通过dispatch() 来触发这些 action 完成回调.

那好我们来看看下面代码:

var Dispatcher = require('./Dispatcher');
var assign = require('object-assign');

var AppDispatcher = assign({}, Dispatcher.prototype, {

  /**
   * A bridge function between the views and the dispatcher, marking the action
   * as a view action.  Another variant here could be handleServerAction.
   * @param  {object} action The data coming from the view.
   */
  handleViewAction: function(action) {
    this.dispatch({
      source: 'VIEW_ACTION',
      action: action
    });
  }

});

module.exports = AppDispatcher;

创建 stores

我们可以通过nodeEventEmitter 来创建store, 我们需要通过EventEmitter 来广播change 事件给相关的controller-view 来看看到底长啥样,

var AppDispatcher = require('../dispatcher/AppDispatcher');
var EventEmitter = require('events').EventEmitter;
var TodoConstants = require('../constants/TodoConstants');
var assign = require('object-assign');

var CHANGE_EVENT = 'change';

var _todos = {}; // collection of todo items

/**
 * Create a TODO item.
 * @param {string} text The content of the TODO
 */
function create(text) {
  // Using the current timestamp in place of a real id.
  var id = Date.now();
  _todos[id] = {
    id: id,
    complete: false,
    text: text
  };
}

/**
 * Delete a TODO item.
 * @param {string} id
 */
function destroy(id) {
  delete _todos[id];
}

// 继承了 EventEmitter
var TodoStore = assign({}, EventEmitter.prototype, {

  /**
   * Get the entire collection of TODOs.
   * @return {object}
   */
  getAll: function() {
    return _todos;
  },

  emitChange: function() {
    this.emit(CHANGE_EVENT);
  },

  /**
   * @param {function} callback
   */
  addChangeListener: function(callback) {
    this.on(CHANGE_EVENT, callback);
  },

  /**
   * @param {function} callback
   */
  removeChangeListener: function(callback) {
    this.removeListener(CHANGE_EVENT, callback);
  },

  // 注册 action
  dispatcherIndex: AppDispatcher.register(function(payload) {
    var action = payload.action;
    var text;

    switch(action.actionType) {
      case TodoConstants.TODO_CREATE:
        text = action.text.trim();
        if (text !== '') {
          create(text);
          TodoStore.emitChange();
        }
        break;

      case TodoConstants.TODO_DESTROY:
        destroy(action.id);
        TodoStore.emitChange();
        break;

      // add more cases for other actionTypes, like TODO_UPDATE, etc.
    }

    return true; // No errors. Needed by promise in Dispatcher.
  })

});

module.exports = TodoStore;

上面代码里有些地方需要说明下, 从最开始的时候我们创建了一个_ tods 的私有对象,这个对象包含了所有的单独todilist 的 item, 因为它在 class 外面存在. 但是却它却却在于 module 的这个闭包里, 这样它就不可以在 module 意外的地方呗修改, 这样就保证了一个直接进出的接口给我们的数据流,保证只有在通过 action 的情况下才可以修改数据.

另外一个地方就是注册 action 的回调函数到dispatcher 上,我们把气球的数据交给个回调函数来处理,并且把它注册在 dispatcher 里.当前我们只负责两个不同的actiontype. 之后我们可以添加更多.

通过controller-view 来监听修改事件

我们需要一个位于组建体系的最上层的React 组件来监听所有的change 事件.在大型应用中我们可能需要更多的组件来监听.可能一个页面里每一个 section 都需要一个监听组件. 下面来看看简单的代码示例, 完整的可以查看github repo todomvc.

var Footer = require('./Footer.react');
var Header = require('./Header.react');
var MainSection = require('./MainSection.react');
var React = require('react');
var TodoStore = require('../stores/TodoStore');

function getTodoState() {
  return {
    allTodos: TodoStore.getAll()
  };
}

var TodoApp = React.createClass({

  getInitialState: function() {
    return getTodoState();
  },

  componentDidMount: function() {
    TodoStore.addChangeListener(this._onChange);
  },

  componentWillUnmount: function() {
    TodoStore.removeChangeListener(this._onChange);
  },

  /**
   * @return {object}
   */
  render: function() {
    return (
      <div>
        <Header />
        <MainSection
          allTodos={this.state.allTodos}
          areAllComplete={this.state.areAllComplete}
        />
        <Footer allTodos={this.state.allTodos} />
      </div>
    );
  },

  _onChange: function() {
    this.setState(getTodoState());
  }

});

module.exports = TodoApp;

到这里,我们已经进入 React的世界,通过使用 react 的生命周期方法,通过getInitialState()初始化这个controller-vew, 然后通过componentDidMount()来完成事件监听, 然后通过componentWillUnmount()来完成清理. 我们渲染了一个div 然后把从 store 哪里拿到的数据填充进去.

关于视图

站在更高一些的角度,我们可以把当前的 React 组件系统理解成如下的结构:

<TodoApp>
  <Header>
    <TodoTextInput />
  </Header>

  <MainSection>
    <ul>
      <TodoItem />
    </ul>
  </MainSection>

</TodoApp>

如果当前进入一个编辑模式,它还会渲染一个TodoTextInput的子类, 让我们来看看这些组件是如何从props 哪里拿到组件然后展示他们的, 然后还有他们如何在 action 和 dispatcher 之间交互通信. 这个MainSection 需要遍历todo item 从 Todoapp 那拿来的 list. 在组件的render() 方法里我们可以这样做:

var allTodos = this.props.allTodos;

for (var key in allTodos) {
  todos.push(<TodoItem key={key} todo={allTodos[key]} />);
}

return (
  <section id="main">
    <ul id="todo-list">{todos}</ul>
  </section>
);

到这里, 我们可以展示每一个 item, 通过 id 可以编辑, 同样可以删除一个 item

var React = require('react');
var TodoActions = require('../actions/TodoActions');
var TodoTextInput = require('./TodoTextInput.react');

var TodoItem = React.createClass({

  propTypes: {
    todo: React.PropTypes.object.isRequired
  },

  render: function() {
    var todo = this.props.todo;

    return (
      <li
        key={todo.id}>
        <label>
          {todo.text}
        </label>
        <button className="destroy" onClick={this._onDestroyClick} />
      </li>
    );
  },

  _onDestroyClick: function() {
    TodoActions.destroy(this.props.todo.id);
  }

});

module.exports = TodoItem;

创建一些语义化的动作方法

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1