react入门之 Redux与flux的比较学习

前言

强烈推荐的学习资源:《深入浅出React和Redux》
此篇学习笔记记录了对书中第三章节的学习心得
在学习之前,对react并没有实践经验,有过Vuex开发经验。但是读完还是收获很大,是用于前期理解Redux概念的一个很好的读物。

总结

1、在学习之前,有过拿redux和vuex进行比较的念头,但是在看完一遍redux之后,发现redux的单向数据流与vuex的双向数据流思想相差还是蛮大的,所以就不比较了.....
2、在阅读这一本书的redux章节当中,书中的讲述顺序十分的棒,从redux前身flux介绍起,逐步深入地过渡到redux,最后再介绍了成熟的react-redux库,这种思路还是蛮不错的。
3、感觉直接看概念,还是没有vuex的思路清晰,因此做了一些拟人化的比喻,个人觉得比较受用...

目录

  1. flux框架介绍
    1.1 flux四大元素
    1.2 flux的拟人化描述
    1.3 flux总结
  2. Redux框架介绍
    2.1 Redux基本原则
    2.2 Redux要素分析
    2.3 Redux的可改进之处
    2.4 搭上大神的顺风车—————— react-redux

1、flux框架介绍

1.1 flux四大元素:

  • Dispatcher:根据注册派发动作(action)
  • Store: 存储数据,处理数据
  • Action:用于驱动Dispatcher
  • View: 用户界面视图
Dispatcher

是全局唯一的Dispatcher对象,关系网的中心

// AppDispatcher.js
// 完成声明即可,后续无需改动
// 注册action等事件主要在store中被调用完成
import {Dispatcher} from 'flux';
export default new Dispatcher ();
Store

注册(register):把当前store注册到Dispatcher下,加入dispatcher关系网
通过emit广播、on挂载事件
store需要注册到全局唯一的Dispatcher上才有效
flux核心部分:当register函数把一个回调函数注册到Dispatcher后,所有派发给Dispatcher的action对象,都会传递到这个回调函数中

const counterValues = {
    'First': 0,
    'Second': 10,
    'Third': 30
}

// 声明、生成store对象
const CounterStore = object.assign({}, EventEmitter.prototype, {
    getCounterValues: function() {
        return counterValues;
    },
    emitChange: function(){
        this.emit(CHANGE_EVENT);             // 广播事件
    },
    addChangeListener: function(){
        this.on(CHANGE_EVENT, callback);     // 挂载事件
    },
    removeChangeListener: fucntion(){
        this.removeListener(CHANGE_EVENT, callback);      // 移除监听
    }
})


// 把CounterStore注册到全局唯一的Dispatcher上,register函数接受一个回调函数做参数
//注册token(控制权令牌)
CounterStore.dispatchToken = AppDispathcer.register((action)=>{
    if(action.type === ActionTypes.INCREMENT){
        // do increment
        // 根据action对象,修改当前store中的counterValues变量
    } else if(action.type === ActionTypes.DECREMENT){
        // do decrement 
        // 根据action对象,修改当前store中的counterValues变量
    }
})   

使用waitFor()函数,通过dispatchToken的传递,实现同步调用,满足多个store之间的相互依赖关系;
常用于获取store中的最新鲜的数据

Action

代表一个动作的纯数据对象
是js对象,且不自带方法,用于驱动Dispatcher,来自用户的请求
Action并不包含数据处理逻辑,而是调用函数,来创建对应的action对象

// ActionTypes.js
export const INCREMENT = 'increment';
export const DECREMENT = 'decrement';
// action.js
import * as ActionTypes form './ActionTypes.js';
import AppDispatcher from './AppDispatcher.js'

export const increment = (counterCaption) => {
    AppDispatcher.dispatch({
        type: ActionTypes.INCREMENT,       // action对象类型
        counterCaption:counterCaption      // 用于标识发出action的来源(即页面元素)
    })
}
View

用户在界面中调用action

// react组件中的事件
onClickBtn() {
    // increment已经在对应的store中完成注册,Dispactcher可识别
    Actions.increment(this, props.caption);
}

1.2 flux的拟人化描述

Dispatcher:
在十字路口中央指挥交通的交警,不会离开工作地点,是唯一的;
当有人督促我派发(dispatch)一下action,我就要打电话给我的小协警了,叫他赶紧来把这个家伙的事处理一下;

Store:
注册:协警把自己的电话号码给了十字路口的交警并告诉他:“发生交通事故,就打这个电话找我,我来处理现场”;
emit广播事件:有人叫我去处理交通事故;
on挂载事件:如果有人叫我去处理交通事故,我要给出的反应;

view
路上的车主,一旦和别人的车撞上了,我要发出一个action(下车跑去找交警告诉交警我的事故属于哪种类型)让交警知道我撞车了,不然他不会理我的;

action
车主督促交警,赶紧把我的action派发(dispatch)出去,让协警快来处理一下;


1.3 flux总结

flux的目的:纠正MVC框架的无法禁绝view与model通信的缺点;

flux的做法:store只有get方法,没有set方法;因此view只能通过get获取store状态,不能修改状态;如果想要修改store状态,只能派发一个action给Dispatcher,由action中ActionTypes对应的store方法去修改store本身。

flux的缺点

  • store之间的依赖:需要建立依赖,需要token
  • 可以但是很难进行服务器端渲染(尚不理解)
  • store替换后,无法保持原有存储状态(指在开发中,store逻辑的修改无法热加载)

2、Redux框架介绍

2.1 Redux基本原则:

  • 继承Flux基本原则:单向数据流
  • 唯一数据源
  • 保持状态只读
  • 只有纯函数能改变数据
1.唯一数据源

Flux:利用Dispatcher的waitFor方法,保证多个store之间的依赖与更新顺序 ==> 数据冗余、应用复杂
Redux:所有state只保存在一个store中,整个应用只有一个store,状态是一个树形对象,每个组件使用状态树上的一部分数据

2.保持状态只读

状态只可读,不可直接修改
渲染原则:UI = render(state) <===> 界面只根据State进行渲染
与Flux相同,必须通过action对象才能修改store状态

3.只有纯函数能改变数据

纯函数:指不依赖于且不改变它作用域之外的变量状态的函数,也就是说函数的返回结果必须完全由传入的参数决定

reducer(state, action)
reducer函数,接受两个参数,第一个参数state是当前的状态(最新的store.state),第二个参数action是接收到的action对象;reducer根据state与action的值产生并返回一个新的对象,返回的这一个对象用来组装新状态对象。


2.2 Redux要素分析

  • Store:存储数据
  • Reducer:根据Action+state替换state,而非直接修改
  • Aciton:定义Action对象
  • Component:调用action
1.Store

在Flux中,Dispatcher的作用:把action对象分发给不同的、已注册的store;
在redux中,只有一个store,是唯一的分发对象,因此将Dispatcher对象简化为store对象中的一个函数dispatch。

使用createStore创建整个应用唯一的store,并且暴露到全局;
组件使用getOwnState函数用于从store中获得状态

import {createStore} from 'redux';
import reducer from './Reducer.js';

const counterValues = {
    'First': 0,
    'Second': 10,
    'Third': 20
}

const store = createStore(reducer, initValues);   // reducer表示更新状态的reducer,initValues是状态的初始值

export default store;

2.Reducer

reducer <===> redux与flux对state的操作差异
flux:直接修改state的值
redux:修改应用状态,并不能直接修改状态上的值,而是创建一个新的状态对象返回给Redux,由Redux组装新状态对象。

// Flux版本的action
// Flux: 直接修改store状态值
CounterStore.dispatchToken = AppDispatcher.register( (action) => {
    if(action.type === AcitonTypes.INCREMENT) {
        counterValues[action.counterCaption]++;
        CounterStore.emitChange();
    } else if(action.type === AcitonTypes.DECREMENT){
        counterValues[action.counterCaption]--;
        CounterStore.emitChange();
    }
})
// Redux版本的action
// 根据state与action的值产生并返回一个新的对象
// 返回的这一个对象用来组装新状态对象。  
function reducer (state,action) {
    const {counterCaption} = action;

    // 返回的对象是一个新的状态树
    // ... 扩展操作符
    // ...state表示扩展拆分state数据,去除了最顶层的树结构,暴露二级节点
    // [counterCaption]: newData 则体现了redux状态树的设计
    // 使用组件的caption作为状态树的对应子数据字段
    switch (action.type) {
        case ActionTypes.INCREMENT: 
            return {...state, [counterCaption]: state[counterCaption] +1};
        case ActionTypes.DECREMENT: 
            return {...state, [counterCaption]: state[counterCaption] -1};
        default: 
            return state
    }
}
Action

flux:Action构造函数不返回什么,而是把构造的动作函数立刻通过调用dispatcher函数派发出去

// Flux之Action
export const increment = (counterCaption) => {
    AppDispatcher.dispatch({
        type: ActionTypes.INCREMENT,       // action对象类型
        counterCaption:counterCaption      // 用于标识发出action的来源(即页面元素)
    })
}

reudx:每个Action构造函数返回一个action对象 == > 返回一个对象,把处理对象的工作交给调用者

// Redux之Action
export const increment = (counterCaption) => {
    return {
        type: ActionTypes.INCREMENT,       // action对象类型
        counterCaption:counterCaption      // 用于标识发出action的来源(即页面元素)
    })
}



Component

用于声明绑定派发action事件

onIncrement() {
    // 通过dispatch派发aciton
    store.dispatch(Actions.increment(this.props.caption));
}

render() {
    const value = this.state.value;
    const {caption} = this.props;
    return (
        <div>
            <button onclick={this.onIncrement}>+</button>
        </div>
    );
}

2.3 Redux的可改进之处

1. 组件功能单一化

当前组件具有两个功能:1、派发Action,更新state树;2、根据state与props渲染用户界面
为了使组件专注于单一功能 ===> 拆分组件(容器组件、展示组件)
容器组件:外层组件,负责与store交互;
展示组件:内层组件,负责渲染界面,无状态;

store 《====》 容器组件 ===(传递props)==》 展示组件 《====》 React界面

2.context全局访问对象

理想目标
单个应用最好只导入一次全局Store,在最顶层React组件的位置;
为提高组件的复用性,其余组件应该避免直接导入Store;
为了满足以上原则而出现的缺点:在一个多层嵌套组件结构中,当只有最里层组件需要使用store,为了将sotre从最外层传到最里层,必须在所有中间组件中使用props逐级传递state。

Context:在树状组件中的所有组件都可访问的一个共同对象,上下文环境
当上级组件宣称自己支持context,并且提供一个函数来返回代表context的对象,所有子孙组件可在宣称(import)后通过this.context访问到这个共同的环境变量。

Provider类组件实现Context

// Provider类组件实现Context

import {PropTypes, Component} from 'react';

class Provider extends Component {
    getChildContext() {
        return {
            store: this.props.store
        };
    }
    
    render() {
        // 渲染子组件   props.children代表子组件
        return this.props.children;
    }
}

// 使Provider被React认可,成为一个Context提供者,必须指定childContextTypes属性
Provider.childContextTypes = {
    store: PropTypes.object
};

Provider提供Context,暴露到所有子组件中

// Provider的实践使用
// index.js  应用入口文件
import store from './Store.js'
import Provider from './Provider.js';

// ControlPanel是ReactDOM的顶层组件,现在被Provider组件包住后,Provider成为顶层组件
// Provider内层包裹的所有
ReactDOM.render (
    <Provider store={store}>
        <ControlPanel />
    </Provider>,
    document.getElementById('root')
)

在子组件中使用Context

// 第一步: 给组件类的contextTypes属性赋值
CounterContainer.contextTypes = {
    store: PropTypes.object
}

// 第二步:在构造函数中用上第二个参数context
constructor(props, context) {
    // 写法一:
    super(props, context);
    // 写法二:
    super(...arguments);
}


// 第三步:通过this.context.store访问store
getOwnState() {
    return {
        // [this.props.catption]用于获取状态树中的某个二级状态
        value: this.context.store.getState()[this.props.catption]
    }
}


2.4 搭上大神的顺风车—————— react-redux

对于Redux的两个改进在实现在实现上仍然具有一定的复杂性与机械性,因此已经有人创建了一个库来帮我们完成这些工作(组件拆分与context)
react-redux库两大功能:
1、connect:连接容器组件与展示组件
2、Provider:我们不再需要自己实现Provider来获取context,可以使用库提供的Provider

connect

包含了两个函数中执行:connectconnect返回函数
connect(mapStateToProps, mapDispatchToProps)的传入参数是两个映射函数,返回值是一个函数
connect函数作用:
1、向内传递state:把store上的状态转化为内层展示组件的props;
2、向外转发Action:把内层展示组件中的用户动作转化为派送给store的动作

export default connect(mapStateToProps, mapDispatchToProps)(Counter);


// mapStateToProps函数,向内传递state => props
function mapStateToProps(state, ownProps) {
    return {
        value: state[ownProps.caption]
    }
}

// mapDispatchToProps函数,向外转发Action
// ownProps属性就是,直接传递给外层容器组件的props
function mapDispatchToProps(dispatch, ownProps) {
    return {
        onIncrement: () => {
            dispatch(Actions.increment(ownProps.caption));
        },
        onDecrement: () => {
            dispatch(Actions.decrement(ownProps.captions))
        }
    }
}

Provider

import {Provider} from 'react-redux';

react-redux库的提供的Provider几乎相同,但更加严谨;
react-redux库要求store必须是一个包含以下三个函数的object:

  • subscribe
  • dispatch
  • getState

react-redux还提供了componentWillReceiveProps钩子,用于每次重新渲染时调用

redux总结 & 与flux的比较

  1. 全局唯一数据源,store
  2. reducer替换状态树,而非直接修改值
  3. Provider优化props传递
  4. 组件拆解,功能单一化

react项目常见组织结构

  • ReactApp
    |---reducers // 所有的reducer
    |---actions // 所有的action构造函数
    |---components // 所有的展示组件
    |---containers // 所有的容器组件
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容