React高级篇(一)从Flux到Redux,react-redux

React框架本身只应用于View,如果基于MVC模式开发,还需要Model和Control层,这样催生了Flux的产生,而Redux是基于Flux理念的一种解决方式。

从《React入门系列》可知,组建直接传递参数或者事件都需要props一层层代理,对于复杂组件,它可能嵌套的子组件非常多,层级也比较深,那么,如果还采用props链条来维护组件通信或者数据共享,将非常困难,也不利于开发和维护。

Flux

Flux框架也是一种MVC框架,不同于传统的MVC,它采用单向数据流,不允许Model和Control互相引用。Flux框架大致如下(来自网络):

image.png
  • Actions: 驱动Dispatcher发起改变
  • Dispatcher: 负责分发动作(事件)
  • Store: 储存数据,处理数据
  • View: 视图部分

Dispatcher只会暴露一个函数-dispatch,接受Action为参数,发起动作。如果需要增加新功能,不需要改变或者增加接口,只需增加Action类型。Dispatch的初始化和使用如下:

// Dispatcher.js
import {Dispatcher} from 'flux';
export default new Dispatcher();

// actions
import AppDispatcher from './Dispatcher.js';

export const increment = (number) => {
  AppDispatcher.dispatch({
    type: 'ADD',
    value: number
  });
};

Store 一般会继承EventEmitter,实现事件监听,发布,卸载。需要将store注册到Dispatcher实例上才能够发挥作用。

Store可以直接修改对象,这点和Redux不同。

import AppDispatcher from './Dispatcher.js';

let value = 10;
const store = Object.assign({}, EventEmitter.prototype, {
  getValue: function() {  
    return value; 
  }

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

  addChangeListener: function(callback) {
    this.on('change', callback);
  },

  removeChangeListener: function(callback) {
    this.removeListener('change', callback);
  }
});

store.dispatchToken = AppDispatcher.register((action) => {
  if (action.type === 'ADD') {
    value = value + action.value;
    store.emitChange();
  }
  // your codes
});
export default store;

view组件中的state应该与Flux store保持一致,如下:

function MyView extend Component {
  constructor(props){
    this.state = { count: store.getValue()}
  }

  // 声明周期函数(组件加载和卸载),需要调用store的事件注册函数,
  // 将处理组件state变化的函数设置为注册函数的回调方法
  componentDidMount() {
    store.addChangeListener(this.onChange);
  }

  componentWillUnmount() {
    store.removeChangeListener(this.onChange);
  }

  onChange() {
    const newCount = store.getValue();
    this.setState({count: newCount});
  }
  // 组件的事件函数,需要调用Action触发状态更新
  onClickIncrementButton() {
    Actions.increment(text);
  }
}

Flux的缺点为:

  1. 一个应用可以拥有多个store,多个store直接可能有依赖关系(相互引用);
  2. Store封装了数据和处理数据的逻辑。

针对Flux的不足,Redux框架出现。

Redux

相比Flux,Redux有如下两个特点:

  1. 在整个应用只提供一个Store,它是一个扁平的树形结构,一个节点状态应该只属于一个组件。
  2. 不允许修改数据。即不能修改老状态,只能返回一个新状态。

Redux数据流如下(来自网络):

image.png

不同于 Flux ,Redux 并没有 dispatcher 的概念(Store已经集成了dispatch方法,所以不需要Dispatcher)。它依赖纯函数来替代事件处理器,这个纯函数叫做Reducer。Reducer在Redux里是个很重要的概念,其封装了处理数据的逻辑。

 在计算机编程中,假如满足下面这两个句子的约束,一个函数可能被描述为一个纯函数:

1. 给出同样的参数值,该函数总是求出同样的结果。
该函数结果值不依赖任何隐藏信息或程序执行处理可能改变的状态或在程序的两个不同的执行。
2. 结果的求值不会促使任何可语义上可观察的副作用或输出。

简单说,一个纯函数,只要输入相同,无论调用多少次,输出都是一样的。这就要求,绝不能修改输入参数,因为输入参数有可能在其他地方用到。下面是一个简单的Reducer对象:

export default (state, action) => {
  switch (action.type) {
    case 'INCREMENT':
      return {...state, value: action.value + 1};
    default:
      return state;
  }
}

把数据逻辑分离开后,Store就变得简单了。它的构造函数需要一个reducer对象(每个组件对应一个reducer,通过combineReducers函数合并N个子reducer为一个主reducer),初始化数据,和中间件(可选)。由于Store已经集成了dispatch方法,所以不需要Dispatcher。一个简单的Store如下:

import {createStore, combineReducers} from 'redux';

const reducer = combineReducers({
    reducer: incrementReducer
});

export default createStore(reducer, {});

View层通过store.dispatch触发动作:

onIncrement() {
   store.dispatch(Actions.increment(value));
}

组件 Context

看到这里,可以发现Flux和Redux都需要显性的在View里面引入store, import store from './Store'。如果可以在一个应用中,只引入一次store,然后所有组件都可以访问到,那该多好?!非常幸运,React提供了这样的功能,即Context。

react context.png

Context就是“上下文环境”,让一个数状组件上所有组件都能访问一个共有的对象。

让顶层容器组件支持Context,那么子组件都可以访问到store,无需各自import。可以如下定义一个顶层组件:

import {PropTypes, Component} from 'react';

class Provider extends Component {
  //必须实现getChildContext方法
  getChildContext() {
    return {
      store: this.props.store
    };
  }

  render() {
    return this.props.children;
  }

}

Provider.propTypes = {
  store: PropTypes.object.isRequired
}
// 必须定义静态属性childContextTypes ,和getChildContext()对应
Provider.childContextTypes = {
  store: PropTypes.object
};

export default Provider;

在入口文件内使用顶层组件:

import React from 'react';
import ReactDOM from 'react-dom';

import store from './Store.js';
import Provider from './Provider.js';

ReactDOM.render(
  <Provider store={store}>
   ...
  </Provider>,
  document.getElementById('root')
);

所有子组件对象都可直接访问到store对象:

const value =  this.context.store.getState();

react-redux

要声明一点,Redux并不是专为React开发的,它可以应用在任何框架上。针对React工程,可以使用react-redux库帮助我们更快,更便捷得搭建Redux工程,让代码更加精简。react-redux库提供了如下功能:

  1. 把组件拆分为容器组件和傻瓜组件,使用者只需要写傻瓜组件;
  2. 使用React的Context提供了一个所有组件都可以直接访问的Context,即react-redux Provider;

于是,我们不需要自己写顶层组件了,只要导入react-redux的Provider,如下:

import React from 'react';
import ReactDOM from 'react-dom';
import {Provider} from 'react-redux';
import store from './Store.js';

ReactDOM.render(
  <Provider store={store}>
    ...
  </Provider>,
  document.getElementById('root')
);

Action和Store写法不变,与Redux相同。

组件变得更加简洁,如下:

function Counter({caption, onIncrement, onDecrement, value}) {
  return (
    <div>
      <button style={buttonStyle} onClick={onIncrement}>+</button>
      <span>{caption} count: {value}</span>
    </div>
  );
}

//store中状态state到傻瓜组件属性props的映射
function mapStateToProps(state, ownProps) {
  return {
    value: state[ownProps.caption]
  }
}

//傻瓜组件中用户的每个动作,都转换为派送给store的动作
function mapDispatchToProps(dispatch, ownProps) {
  return {
    onIncrement: () => {
      dispatch(Actions.increment(ownProps.caption));
    }
  }
}
// connent函数:连接容器组件和傻瓜组件
export default connect(mapStateToProps, mapDispatchToProps)(Counter);

可以看到,用了react-redux之后,代码精简不少,而且逻辑更加清晰。

小结

从Flux到Redux,再到react-redux,从这个简短历程中,我们可以看到框架设计上的演进,而redux + react-redux也是React开发万家桶的标配。到了这里,可以忘记Flux啦~

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

推荐阅读更多精彩内容