redux+flux(一:入门篇)
React是facebook推出的js框架,React 本身只涉及UI层,如果搭建大型应用,必须搭配一个前端框架。也就是说,你至少要学两样东西,才能基本满足需要:React + 前端框架。
Facebook官方使用的是 Flux 框架。本文就介绍如何在 React 的基础上,使用 Flux 组织代码和安排内部逻辑。
首先,Flux将一个应用分成四个部分:
Flux 的最大特点,就是数据的"单向流动"。
- 用户访问 View
- View 发出用户的 Action
- Dispatcher 收到 Action,要求 Store 进行相应的更新
- Store 更新后,发出一个"change"事件
- 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
属性(说明动作的类型)和一些其他属性(用来传递数据)。 - // index.jsx
- // 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个东西,有点搞人呀。。。本文参考了许多很好的文章和博客
- //Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。
- // actions/ButtonActions.js
redux+flux(一:入门篇)的更多相关文章
- Pandas系列之入门篇
Pandas系列之入门篇 简介 pandas 是 python用来数据清洗.分析的包,可以使用类sql的语法方便的进行数据关联.查询,属于内存计算范畴, 效率远远高于硬盘计算的数据库存储.另外pand ...
- Membership三步曲之入门篇 - Membership基础示例
Membership 三步曲之入门篇 - Membership基础示例 Membership三步曲之入门篇 - Membership基础示例 Membership三步曲之进阶篇 - 深入剖析Pro ...
- spring boot(一):入门篇
构建微服务:Spring boot 入门篇 什么是spring boot Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新Spring应用的初始搭建以及开发过程.该框 ...
- 1. web前端开发分享-css,js入门篇
关注前端这么多年,没有大的成就,就入门期间积累了不少技巧与心得,跟大家分享一下,不一定都适合每个人,毕竟人与人的教育背景与成长环境心理活动都有差别,但就别人的心得再结合自己的特点,然后探索适合自己的学 ...
- 一个App完成入门篇(七)- 完成发现页面
第七章是入门篇的倒数第二篇文章了,明天整个APP将进入收官. 本节教程主要要教会大家使用二维码扫描和用do_WebView组件加在html页面. 导入项目 do_WebView组件 扫描功能 自定义事 ...
- [原创]Linq to xml增删改查Linq 入门篇:分分钟带你遨游Linq to xml的世界
本文原始作者博客 http://www.cnblogs.com/toutou Linq 入门篇(一):分分钟带你遨游linq to xml的世界 本文原创来自博客园 请叫我头头哥的博客, 请尊重版权, ...
- 转:OSGi 入门篇:模块层
OSGi 入门篇:模块层 1 什么是模块化 模块层是OSGi框架中最基础的一部分,其中Java的模块化特性在这一层得到了很好的实现.但是这种实现与Java本身现有的一些模块化特性又有明显的不同. 本文 ...
- 转:OSGi 入门篇:生命周期层
OSGi 入门篇:生命周期层 前言 生命周期层在OSGi框架中属于模块层上面的一层,它的运作是建立在模块层的功能之上的.生命周期层一个主要的功能就是让你能够从外部管理应用或者建立能够自我管理的应用(或 ...
- 【three.js详解之一】入门篇
[three.js详解之一]入门篇 开场白 webGL可以让我们在canvas上实现3D效果.而three.js是一款webGL框架,由于其易用性被广泛应用.如果你要学习webGL,抛弃那些复杂的 ...
- [Maven]Apache Maven 入门篇
作者:George Ma 上 写这个 maven 的入门篇是因为之前在一个开发者会的动手实验中发现挺多人对于 maven 不是那么了解,所以就有了这个想法.这个入门篇分上下两篇.本文着重动手,用 ma ...
随机推荐
- ABP理论学习之审计日志
返回总目录 本篇目录 介绍 配置 通过特性开启/关闭 注意 我项目中的例子 介绍 维基百科说: "审计跟踪(也叫审计日志)是与安全相关的按照时间顺序的记录,记录集或者记录源,它们提供了活动序 ...
- NodeJs 开发微信公众号(一)准备工作
前言 大概是一个月前,自己用业余时间做了一个微信公众号.微信开发,尤其是对后台不熟悉的人来说显得尤其困难.首先要克服的是后台语言(nodejs)的一些不熟悉困难,其次,也是最大的一点困难是在跟微信交互 ...
- 《CLR.via.C#第三版》第二部分第8,9章节读书笔记(四)
三种类型的构造方法: 实例构造器(引用类型):实例构造器永远不能被继承(所以方法前没有修饰符):如果类的修饰符为static(sealed和abstract),编译器根本不会在类的定义中生成一个默认构 ...
- linux系统编程之进程(八):守护进程详解及创建,daemon()使用
一,守护进程概述 Linux Daemon(守护进程)是运行在后台的一种特殊进程.它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件.它不需要用户输入就能运行而且提供某种服务,不是对整个 ...
- Spring:源码解读Spring IOC原理
Spring IOC设计原理解析:本文乃学习整理参考而来 一. 什么是Ioc/DI? 二. Spring IOC体系结构 (1) BeanFactory (2) BeanDefinition 三. I ...
- 在MotionBuilder中绑定C3D动作和模型
[题外话] 实验室人手不足,虽然自己连MotionBuilder一点都没有用过,但是老板叫自己干也只能硬着头皮上了.本文详细介绍了MotionBuilder 2013中的摄像机操作以及在MotionB ...
- Android动画小记录
今天在做一个头部滑动菜单的时候需要使用TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYD ...
- js框架模版
(function() { //注册命名空间zzw到window对象上 window['zzw'] = {} //定义一个$函数 function $() { alert("hello $& ...
- ANDROID下面的游戏更新目录
更新模块的整体方案终于搞定了,包括launcher的自更新,以及framework,app等代码的更新,均测试通过. 很激动地拿到ANDROID上去测试,一下就傻眼了. 在创建upd目录的时候,就遇上 ...
- struts1二:基本环境搭建
首先建立一个web项目 引入需要的jar包 建立包com.bjpowernode.struts创建LoginAction package com.bjpowernode.struts; import ...