React是facebook推出的js框架,React 本身只涉及UI层,如果搭建大型应用,必须搭配一个前端框架。也就是说,你至少要学两样东西,才能基本满足需要:React + 前端框架。

Facebook官方使用的是 Flux 框架。本文就介绍如何在 React 的基础上,使用 Flux 组织代码和安排内部逻辑。

首先,Flux将一个应用分成四个部分:

Flux 的最大特点,就是数据的"单向流动"。

  1. 用户访问 View
  2. View 发出用户的 Action
  3. Dispatcher 收到 Action,要求 Store 进行相应的更新
  4. Store 更新后,发出一个"change"事件
  5. View 收到"change"事件后,更新页面.
     // index.jsx
    var React = require('react');
    var ReactDOM = require('react-dom');
    var MyButtonController = require('./components/MyButtonController'); ReactDOM.render(
    <MyButtonController/>,
    document.querySelector('#example') //渲染组件到id=example的元素中
    );
     // components/MyButtonController.jsx
    var React = require('react');
    var ButtonActions = require('../actions/ButtonActions');
    var MyButton = require('./MyButton'); var MyButtonController = React.createClass({
    createNewItem: function (event) {
    ButtonActions.addNewItem('new item'); //这里是什么? buttonActions
    }, render: function() {
    return <MyButton
    onClick={this.createNewItem} //看到这里下面肯定还会有个button组件 会调用此处的onclick事件
    />;
    }
    }); module.exports = MyButtonController;
     // components/MyButton.jsx
    var React = require('react'); var MyButton = function(props) {
    return <div>
    <button onClick={props.onClick}>New Item</button> //果然没错吧 通过prop调用在上面定义好的促发函数
    </div>;
    }; module.exports = MyButton;

    每个Action都是一个对象,包含一个actionType属性(说明动作的类型)和一些其他属性(用来传递数据)。

  6.  // actions/ButtonActions.js
    var AppDispatcher = require('../dispatcher/AppDispatcher'); var ButtonActions = {
    addNewItem: function (text) { //现在知道上面的调用是怎么来的了 就是此处
    AppDispatcher.dispatch({
    actionType: 'ADD_NEW_ITEM',
    text: text
    });
    },
    };
     // dispatcher/AppDispatcher.js
    var ListStore = require('../stores/ListStore'); AppDispatcher.register(function (action) { //register和dispatch是相对的 一个是注册事件 一个是派发事件和数据
    switch(action.actionType) { //这里的action是.dispatch方法里的参数
    case 'ADD_NEW_ITEM':
    ListStore.addNewItemHandler(action.text); //当传过来的actionType = ‘add——new——item’ 会发生这个事件 往下看
    ListStore.emitChange(); //这里添加玩内容后,自动触发下面的emitchangge事件 往下看
    break;
    default:
    // no op
    }
    })

    Dispatcher 的作用是将 Action 派发到 Store、。你可以把它看作一个路由器,负责在 View 和 Store 之间,建立 Action 的正确传递路线。注意,Dispatcher 只能有一个,而且是全局的。

     // stores/ListStore.js
    var EventEmitter = require('events').EventEmitter;
    var assign = require('object-assign'); var ListStore = assign({}, EventEmitter.prototype, { //es6浅克隆方法
    items: [], getAll: function () {
    return this.items;
    }, addNewItemHandler: function (text) { //就是这里 上面调用了这个函数 就是添加内容的实现
    this.items.push(text);
    }, emitChange: function () {
    this.emit('change');
    }, addChangeListener: function(callback) {
    this.on('change', callback); //监听change事件 然后进行回调
    }, removeChangeListener: function(callback) {
    this.removeListener('change', callback);
    }
    });

    Store 保存整个应用的状态。它的角色有点像 MVC 架构之中的Model 。上面代码中,ListStore继承了EventEmitter.prototype,因此就能使用ListStore.on()ListStore.emit(),来监听和触发事件了。

     // components/MyButtonController.jsx  修改后完整的view代码
    var React = require('react');
    var ListStore = require('../stores/ListStore');
    var ButtonActions = require('../actions/ButtonActions');
    var MyButton = require('./MyButton'); var MyButtonController = React.createClass({
    getInitialState: function () {
    return {
    items: ListStore.getAll() //初始化state是根据listStore里面取出来的
    };
    }, componentDidMount: function() {
    ListStore.addChangeListener(this._onChange); //当listStore促发change事件会导致 该组件调用_onChange()
    }, componentWillUnmount: function() {
    ListStore.removeChangeListener(this._onChange);
    }, _onChange: function () {
    this.setState({
    items: ListStore.getAll() //重新填充数据
    });
    }, createNewItem: function (event) {
    ButtonActions.addNewItem('new item');
    }, render: function() {
    return <MyButton
    items={this.state.items}
    onClick={this.createNewItem}
    />;
    }
    });

    上面代码中,你可以看到当MyButtonController 发现 Store 发出 change 事件,就会调用 this._onChange 更新组件状态,从而触发重新渲染。

     // components/MyButton.jsx  最终的mybutton
    var React = require('react'); var MyButton = function(props) {
    var items = props.items; //prop调用上级组件的参数
    var itemHtml = items.map(function (listItem, i) {
    return <li key={i}>{listItem}</li>;
    }); return <div>
    <ul>{itemHtml}</ul>
    <button onClick={props.onClick}>New Item</button>
    </div>;
    }; module.exports = MyButton;

    关于redux:如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性,下面描述了需要redux的情景

    • 用户的使用方式复杂
    • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
    • 多个用户之间可以协作
    • 与服务器大量交互,或者使用了WebSocket
    • View要从多个来源获取数据
       //Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
      import { createStore } from 'redux';
      const store = createStore(fn);
      当前时刻的 State,可以通过store.getState()拿到。
      const state = store.getState();
      Redux 规定, 一个 State 对应一个 View。只要 State 相同,View 就相同。 State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。 const action = {
      type: 'ADD_TODO',
      payload: 'Learn Redux'
      };//上面代码中,Action 的名称是ADD_TODO,它携带的信息是字符串Learn Redux。 const ADD_TODO = '添加 TODO'; function addTodo(text) {
      return {
      type: ADD_TODO,
      text
      }
      } const action = addTodo('Learn Redux');
      //上面代码中,addTodo函数就是一个 Action Creator。 store.dispatch({
      type: 'ADD_TODO',
      payload: 'Learn Redux'
      }); //store.dispatch接受一个 Action 对象作为参数,将它发送出去。 Store 收到 Action 以后,必须给出一个新的 State,这样 View 才会发生变化。这种 State 的计算过程就叫做 Reducer。

      const defaultState = 0;
      const reducer = (state = defaultState, action) => {
      switch (action.type) {
      case 'ADD':
      return state + action.payload;
      default:
      return state;
      }
      }; import { createStore } from 'redux';
      const store = createStore(reducer); //fn就是这里的reducer //上面代码中,createStore接受 Reducer 作为参数,生成一个新的 Store。以后每当store.dispatch发送过来一个新的 Action,就会自动调用 Reducer,得到新的 State。 //Store 允许使用store.subscribe方法设置监听函数,一旦 State 发生变化,就自动执行这个函数。
      const store = createStore(reducer);
      store.subscribe(listener); 显然,只要把 View 的更新函数(对于 React 项目,就是组件的render方法或setState方法)放入listen,就会实现 View 的自动渲染。 let unsubscribe = store.subscribe(() =>
      console.log(store.getState())
      );
      unsubscribe(); //store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

      下面是createStore方法的一个简单实现,可以了解一下 Store 是怎么生成的。

       import { createStore } from 'redux';
      let { subscribe, dispatch, getState } = createStore(reducer); const createStore = (reducer) => {
      let state;
      let listeners = []; const getState = () => state; const dispatch = (action) => {
      state = reducer(state, action); //派发action执行state变更
      listeners.forEach(listener => listener()); //调用所有监听函数
      }; const subscribe = (listener) => { //添加监听的事件
      listeners.push(listener);
      return () => {
      listeners = listeners.filter(l => l !== listener);
      }
      }; dispatch({}); return { getState, dispatch, subscribe }; //返回的就是这3个const
      };

      Reducer 函数负责生成 State。由于整个应用只有一个 State 对象,包含所有数据,对于大型应用来说,这个 State 必然十分庞大,导致 Reducer 函数也十分庞大。

       const chatReducer = (state = defaultState, action = {}) => {
      const { type, payload } = action;
      switch (type) {
      case ADD_CHAT:
      return Object.assign({}, state, {
      chatLog: state.chatLog.concat(payload)
      });
      case CHANGE_STATUS:
      return Object.assign({}, state, {
      statusMessage: payload
      });
      case CHANGE_USERNAME:
      return Object.assign({}, state, {
      userName: payload
      });
      default: return state;
      }
      };//上面代码中,三种 Action 分别改变 State 的三个属性。

      Redux 提供了一个combineReducers方法,用于 Reducer 的拆分。你只要定义各个子 Reducer 函数,然后用这个方法,将它们合成一个大的 Reducer。

      import { combineReducers } from 'redux';
      
      const chatReducer = combineReducers({
      chatLog,
      statusMessage,
      userName
      }) export default todoApp; //下面是combineReducer的简单实现。
      const combineReducers = reducers => {
      return (state = {}, action) => {
      return Object.keys(reducers).reduce(
      (nextState, key) => {
      nextState[key] = reducers[key](state[key], action);
      return nextState;
      },
      {}
      );
      };
      }; //你可以把所有子 Reducer 放在一个文件里面,然后统一引入。
      import { combineReducers } from 'redux'
      import * as reducers from './reducers' const reducer = combineReducers(reducers)
       //listener可以通过store.getState()得到当前状态。如果使用的是 React,这时可以触发重新渲染 View。
      // 设置监听函数
      store.subscribe(listener);
      function listerner() {
      let newState = store.getState();
      component.setState(newState);
      }

      来一个实例练练手

       //redux/index.js
      export default (state = 0, action) => { //每次有dispatch过来 根据action.type促发相应事件 返回一个新的state
      switch (action.type) {
      case 'INCREMENT':
      return state + 1
      case 'DECREMENT':
      return state - 1
      default:
      return state
      }
      }
       import React, { Component, PropTypes } from 'react'
      
       class Counter extends Component {
      static propTypes = { //这里对数据类型做了规范
      value: PropTypes.number.isRequired,
      onIncrement: PropTypes.func.isRequired,
      onDecrement: PropTypes.func.isRequired
      } incrementIfOdd = () => {
      if (this.props.value % 2 !== 0) {
      this.props.onIncrement()
      }
      } incrementAsync = () => { //异步调用 等待1s执行prop上的 onIncrement
      setTimeout(this.props.onIncrement, 1000)
      } render() {
      const { value, onIncrement, onDecrement } = this.props //注意这里的用法 等下看下调用该组件的是哪一个
      return (
      <p>
      Clicked: {value} times
      {' '}
      <button onClick={onIncrement}>
      +
      </button>
      {' '}
      <button onClick={onDecrement}>
      -
      </button>
      {' '}
      <button onClick={this.incrementIfOdd}>
      Increment if odd
      </button>
      {' '}
      <button onClick={this.incrementAsync}>
      Increment async
      </button>
      </p>
      )
      }
      } export default Counter
       import React from 'react'
      import ReactDOM from 'react-dom'
      import { createStore } from 'redux'
      import Counter from './components/Counter'
      import counter from './reducers' const store = createStore(counter)
      const rootEl = document.getElementById('root') const render = () => ReactDOM.render(
      <Counter //这里的prop浮出水面 往上面看属性是否对应
      value={store.getState()}
      onIncrement={() => store.dispatch({ type: 'INCREMENT' })}
      onDecrement={() => store.dispatch({ type: 'DECREMENT' })}
      />,
      rootEl
      ) render()
      store.subscribe(render) //每回state变化 就会从新render()

      希望对大家有帮助,今天就一直在研究这2个东西,有点搞人呀。。。本文参考了许多很好的文章和博客

redux+flux(一:入门篇)的更多相关文章

  1. Pandas系列之入门篇

    Pandas系列之入门篇 简介 pandas 是 python用来数据清洗.分析的包,可以使用类sql的语法方便的进行数据关联.查询,属于内存计算范畴, 效率远远高于硬盘计算的数据库存储.另外pand ...

  2. Membership三步曲之入门篇 - Membership基础示例

    Membership 三步曲之入门篇 - Membership基础示例 Membership三步曲之入门篇 -  Membership基础示例 Membership三步曲之进阶篇 -  深入剖析Pro ...

  3. spring boot(一):入门篇

    构建微服务:Spring boot 入门篇 什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框 ...

  4. 1. web前端开发分享-css,js入门篇

    关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人与人的教育背景与成长环境心理活动都有差别,但就别人的心得再结合自己的特点,然后探索适合自己的学 ...

  5. 一个App完成入门篇(七)- 完成发现页面

    第七章是入门篇的倒数第二篇文章了,明天整个APP将进入收官. 本节教程主要要教会大家使用二维码扫描和用do_WebView组件加在html页面. 导入项目 do_WebView组件 扫描功能 自定义事 ...

  6. [原创]Linq to xml增删改查Linq 入门篇:分分钟带你遨游Linq to xml的世界

    本文原始作者博客 http://www.cnblogs.com/toutou Linq 入门篇(一):分分钟带你遨游linq to xml的世界 本文原创来自博客园 请叫我头头哥的博客, 请尊重版权, ...

  7. 转:OSGi 入门篇:模块层

    OSGi 入门篇:模块层 1 什么是模块化 模块层是OSGi框架中最基础的一部分,其中Java的模块化特性在这一层得到了很好的实现.但是这种实现与Java本身现有的一些模块化特性又有明显的不同. 本文 ...

  8. 转:OSGi 入门篇:生命周期层

    OSGi 入门篇:生命周期层 前言 生命周期层在OSGi框架中属于模块层上面的一层,它的运作是建立在模块层的功能之上的.生命周期层一个主要的功能就是让你能够从外部管理应用或者建立能够自我管理的应用(或 ...

  9. 【three.js详解之一】入门篇

    [three.js详解之一]入门篇   开场白 webGL可以让我们在canvas上实现3D效果.而three.js是一款webGL框架,由于其易用性被广泛应用.如果你要学习webGL,抛弃那些复杂的 ...

  10. [Maven]Apache Maven 入门篇

    作者:George Ma 上 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法.这个入门篇分上下两篇.本文着重动手,用 ma ...

随机推荐

  1. CAD调试时抛出“正试图在 os 加载程序锁内执行托管代码。不要尝试在 DllMain 或映像初始化函数内运行托管代码”异常的解决方法

    这些天重装了电脑Win10系统,安装了CAD2012和VS2012,准备进行软件开发.在调试程序的时候,CAD没有进入界面就抛出 “正试图在 os 加载程序锁内执行托管代码.不要尝试在 DllMain ...

  2. JQuery中的dialog使用介绍

    初始化参数 对于 dialog 来说,首先需要进行初始化,在调用 dialog 函数的时候,如果没有传递参数,或者传递了一个对象,那么就表示在初始化一个对话框. 没有参数,表示按照默认的设置初始化对话 ...

  3. eclipse 下找不到或无法加载主类的解决办法

    有时候 Eclipse 会发神经,好端端的 project 就这么编译不了了,连 Hello World 都会报“找不到或无法加载主类”的错误,我已经遇到好几次了,以前是懒得深究就直接重建projec ...

  4. Javaee中文乱码解决方法

    分类: javaee2015-07-09 16:35 29人阅读 评论(0) 收藏 编辑 删除 post 中文乱码解决方式 接受数据的时候设置 request.setCharacterEncoding ...

  5. 关于EF6的记录Sql语句 与 EntityFramework.Extend 的诟病

    1.关于EF6的记录Sql语句,一个老生长谈的问题. 他生成的sql语句实在是烂,大家都这样说 2.EF6 更新删除不方便,没有批量操作.所以,有人出了EF6.Extend  大家用起来也很爽 基于以 ...

  6. Java多线程7:死锁

    前言 死锁单独写一篇文章是因为这是一个很严重的.必须要引起重视的问题.这不是夸大死锁的风险,尽管锁被持有的时间通常很短,但是作为商业产品的应用程序每天可能要执行数十亿次获取锁->释放锁的操作,只 ...

  7. 多线程中的锁系统(二)-volatile、Interlocked、ReaderWriterLockSlim

    上章主要讲排他锁的直接使用方式.但实际当中全部都用锁又太浪费了,或者排他锁粒度太大了,本篇主要介绍下升级锁和原子操作. 阅读目录 volatile Interlocked ReaderWriterLo ...

  8. 前端er是否忽略了某些东西?——读《ppk谈JavaScript》

    关于书 “不知道ppk的网站QuirksMode,说明你可能还没有真正成为资深的JavaScript程序员.” ——Roger Johansson,瑞典资深Web专家. ppk是世界级前端技术专家,W ...

  9. OpenGL学习资料汇总

    我学OpenGL的3D编程也有1.2个年头了,走了很多弯路,也算有点收获.现在整理出一些好用的资料如下. NeHe OpenGL教程中文版 地址(http://www.yakergong.net/ne ...

  10. 模糊测试(fuzz testing)介绍(一)

    模糊测试(fuzz testing)是一类安全性测试的方法.说起安全性测试,大部分人头脑中浮现出的可能是一个标准的“黑客”场景:某个不修边幅.脸色苍白的年轻人,坐在黑暗的房间中,正在熟练地使用各种工具 ...