一、目的

最近学习应用React,之前在vue的时候已经接触过vuex,它本身也是参照了flux的思想,实现和使用更加的简练。

bg2016011503.png

flux地址:

我没有上来就去看Redux,而是先搞了一下麻烦的flux,说麻烦是因为真的很多样板代码,一个简单todos也催生了很多代码。

这个todosflux应用的demo,只不过自己用自己的一些逻辑和代码实现出来。

不谈有关flux的基本思想和原理,因为这个网上太多了。

只是说说,具体的flux思想实现的todos

Flux实现使用的facebook的实现.

二、介绍

1、项目结构:只谈src

|—— src
  |—— actions
    |—— TodoActions.js   // flux action
  |—— components
    |—— TodoComponents   // todo组件
      |—— TodoApp.jsx    // 整体组件
      |—— TodoFooter.jsx // 底部组件
      |—— TodoHeader.jsx // 头部组件
      |—— TodoItem.jsx   // 单个todo
      |—— TodoList.jsx   // todo list
      |—— TodoTextInput.jsx // 头部输入框组件
  |—— constants
    |—— TodoConstants.js // 常量存储   
  |—— dispatcher
    |—— TodoDispatcher.js// dispatcher
  |—— stores
    |—— TodoStores.js    // store
  |—— stylesheets
  |—— App.jsx            // 总组件
  |—— main.js            // 入口文件  

其中项目生成使用的是我自己搞的一个快速生成 webpack3+react16的项目开发环境,github地址:

2、Todos拥有的功能和最终结果:

主要有以下功能:

  • 增加新的 todo
  • 修改某个 todo 的完成状态
  • 修改所有的 todo 的完成状态
  • 删除某个 todo
  • 点击某个 todo 进行更新
  • 统计未完成的 todo 的数量 (底部 3 items lest

1.png

三、实现

因为代码众多,所以只通过简单的一两个例子说明,源代码我放在了github上【底部有地址】.

每一个 todo 是一个对象:

{ id: 5, text: 'FFFFFFFFFF', complete: true }

更新某个 todo 的完成状态 (complete) 举例:

使用flux实现这个功能的基本流程:

1. TodoItem.jsx 中的点击事件

首先,按钮是在 TodoItem.jsx组件中的,但是我的实际操作事件是在TodoList.jsx中进行的,因此这就涉及到props的传值问题。

注意,这并不是简单的从TodoList.jsx传递过来函数这么简单,因为更新某个组件的完成状态需要传递一个id

因此不能简单的在子组件中直接应用父组件传递的props的函数,需要通过这种的方式:

TodoItem.jsx中的部分代码:

  // toggle complete 
  // 触发 toggle complete 事件
  toggleClickHandle(event) {
    const id = Number.parseInt(this.refs.itemContainer.getAttribute("value"));
    this.props.toggleClick(id);
  }

上面的点击事件是定义在TodoItem.jsx中的,它主要完成两步:

  1. 获取到该item的id(我尝试使用state中存储id,但是发现后面无法查找到正确的,这个问题我暂时没解决,因此用了一个 attribute来存放id。
  2. 触发父组件(TodoList.jsx)传递过来的props.toggleClick 同时传递参数id. 这样子,父组件中,能够正确的使用参数 id

2、触发TodoList.jsx 中的处理事件

上一步实际上最终还是触发了TodoList.jsx的事件:

该事件定义如下(在TodoList.jsx)中:

  // 按钮的toggle complete事件
  toggleComplete(id){
    // 通过 action 触发 dispatcher
    TodoAction.toggleComplete(id);
  }

这个方法其实也非常简单,就是触发 TodoActiontoggleComplete方法:

  • 根据flux的设计思想,view的事件,通过action在触发Dispatcher,然后进行Store的更新操作(最上面的图)

3、TodoActions.js 中的 toggleComplete 方法

上面触发了TodoAction.toggleComplete,而在TodoActions.js中如下定义:

import TodoConstants from '../constants/TodoConstants';
import TodoDispatcher from '../dispatcher/TodoDispatcher';
const TodoActions = {
  // 此处省略其他的action
  // 用于更改某个item完成状态的action
  toggleComplete(id){ 
    TodoDispatcher.handleAction({
      actionType:TodoConstants.TOGGLE_COMPLETE,
      data:id
    });
  },
}
export default TodoActions;

因此,在Actions中,实际上是触发了 TodoDispatcher.handleAction,并且给了一个actionTypeTodoConstants.TOGGLE_COMPLETE(在 TodoConstants.js 中定义的常量)和一个 data:id

4、Dispatcher的action转发(转发一次不恰当)

import { Dispatcher } from 'flux';
const TodoDispatcher = new Dispatcher();
TodoDispatcher.handleAction = function (action) {
  this.dispatch({
    source: 'VIEW_ACTION',
    action: action
  });
};
export default TodoDispatcher;

Dispatcher 实际上就是将 action 给dispatch掉。(dispach本身有调度的意思)

因此在注册了 dispatcher 的地方会收到 TodoDispatcher中对action的调度,而 注册 是在 TodoStore 中的

5、TodoStores.js

实际上 TodoStore.js 有两部分内容,一部分是对数据的存储和更改,另一个部分是dispatcher的注册。

1)数据的存储和修改

最终数据修改和存放都是在Store上的,因此Store的内容是最多的:

import { EventEmitter } from 'events';

import TodoConstants from '../constants/TodoConstants';
import TodoDispatcher from '../dispatcher/TodoDispatcher';
// 触发事件的名称
const CHANGE_EVENT = 'change';
// 存放数据
const _store = {
  todoList: [
    { id: 0, text: 'AAAAAAAAAA', complete: true },
  ]
};
// 更改是否完成的状态 toggle
const toggleComplete = function (id) {
  for (let i = 0, len = _store.todoList.length; i < len; i++) {
    if (_store.todoList[i].id === Number.parseInt(id)) {
      _store.todoList[i].complete = !_store.todoList[i].complete;
      break;
    }
  }
}
// 构造 todoSTore
const TodoStore = Object.assign({}, EventEmitter.prototype, {
  addChangeListener: function (cb) {
    this.on(CHANGE_EVENT, cb);
  },
  removeChangeListener: function (cb) {
    this.removeListener(CHANGE_EVENT, cb);
  },
  getTodoList: getTodoList, // 获取所有的todoItem
  getLeftItems: getLeftItems, // 获取所有的未完成
});

其中比较关键的一点在于,在构造todoStore的时候,实际上使用了EventEmitter,主要是为了能够在组件中响应change事件(下面会说道)。

Store中内容的更改,最终是为了能够更新到view,这也是flux设计思想(单项数据流)的一部分,因此EventEmitter无非就是使用它的事件监听和响应机制而已。

2) Dispatcher.register

Store中还有重要的一部分就是Dispatcher的调度应用(不仅仅是调度)。

TodoDispatcher.handleAction中的dispatch应用到注册了 Dispatcher 上面.

// 注册 Dispatcher
TodoDispatcher.register((payload) => {
  const action = payload.action;
  switch (action.actionType) {
    // toggle complete 事件
    case TodoConstants.TOGGLE_COMPLETE:
      toggleComplete(action.data);
      TodoStore.emit(CHANGE_EVENT);
      break;
    // 省略了其他的一些case
    default:
      return true;
      break;
  }
});

可以看到上面的register中,参数payload实际上就是在TodoDispatcher.handleAction里面dispatch的内容。

TodoAction在触发TodoDispatcher的时候传递了actionTypedata两个参数。

因此在TodoDispatcher.register中,通过actionType来判断应该进行怎样的操作。

而实际上真正的修改是发生在toggleComplete(action.data);这个函数中,这个函数在TodoStores.js中定义(看上面第1小点)

修改完成后,需要触发更新事件TodoStore.emit(CHANGE_EVENT);,从而 view 能够拿到更新通知,触发回调函数

6、组件的更新:

在Store更新完触发TodoStore.emit之后,监听事件的回调便会被触发,而这个回调我放在了TodoApp.jsx中:

TodoAoo.jsx

import React, { Component } from 'react';

import '../../stylesheets/scss/todo.scss';

import TodoHeader from './TodoHeader';
import TodoList from './TodoList';
import TodoFooter from './TodoFooter';

import TodoStore from '../../stores/TodoStores';

class TodoApp extends Component {
  constructor(props) {
    super(props);
    this.state = {
      list: TodoStore.getTodoList(),
      leftItems: TodoStore.getLeftItems(),
      completed: TodoStore.getAllCompleted()
    }
    this._onChange = this._onChange.bind(this);
  }
  // 组件挂在前 将事件进行监听
  componentWillMount() {
    // 将 action的create 添加到store的event中
    TodoStore.addChangeListener(this._onChange);
  }
  // 组件销毁前 撤销事件的监听
  componentWillUnmount() {
    TodoStore.removeChangeListener(this._onChange);
  }
  // 生成某一个主要的
  _onChange() {
    this.setState({
      list: TodoStore.getTodoList(),
      leftItems: TodoStore.getLeftItems(),
      completed: TodoStore.getAllCompleted()
    });
  }
...... 其他代码省略

为了方便起见,其实我就用了一个_onChange()作为回调,组件挂在前,便通过TodoStore.addChangeListener(this._onChange); (这个在TodoStore中定义的)监听change事件。

每次我的store修改完成之后,便触发 _onChange(),从而获取新的列表和状态。

四、代码

可以看出一个简单的toggleComplete就需要如此多的代码,不过对于应用和理解flux的设计思想还是有好处的。

我把代码放在了github和gitosc上,可以下载完成代码查看:

GITHUB:

GITOSC: