ReactNative系列(七):Redux和React-Redux详解

ReactNative.jpg

ReactNative整理:《ReactNative系列》

内容目录

一、简介
二、Redux使用场景
三、Redux组成部分
   3.1 Action
   3.2 Reducer
   3.3 Store
四、React-Redux介绍
   4.1 React-Redux 组件分离思想
   4.2 connect 方法
   4.3 Provider
五、简单示例


一、简介

  • 什么是Redux

Redux是 JavaScript 状态容器,提供可预测化的状态管理。对可预测化的个人理解:每个状态都是由action触发旧state更新生成新的state,生成结果可控,并且来源都只有一个 -- store,所以是可预测的。

  • 为什么要用Redux

 通俗来讲:由于多个路由页面或组件之间消息传递或相互状态控制太过繁琐,需要中间页面传递消息,通过propsstate流经多个组件来实现。这样会使通信出错的概率大增,页面越复杂交互越多,这种情况就越严重,所以需要一个可以统筹全局数据状态的工具,用作接收与分发,这时就需要用到Redux
从网上找到张图片,可以形象的表示Redux的功能。

Redux作用.png

  • Redux的三大原则

Redux的使用需要遵循三大原则,这里先简单介绍,下文会详细解释。

1、单一数据源
redux的数据都存放在store中,保证数据来源的唯一性,同时更便于应用的数据交互;
2、state是只读状态
action可以去取state的值,但是不能直接对state进行修改;
3、使用纯函数修改state
 利用reducer改变原有的state状态,并生成一个新的state返回

Redux文档:Redux文档地址

二、Redux使用场景

首先,我们需要明确:Redux是很有用的框架,但并不是非用不可。

什么场景下可以不用

  • 用户使用方式简单,没有太过复杂的交互(这个就不用多解释了)
  • 数据来源单一:比方说,数据都是由父组件传递过来,只在当前组件中使用;或者是从服务端拉取,同样只在当前组件中使用。
  • 不需要大量与服务器交互

上面情况都可以不用Redux,那么与之相反的情况下就需要用到Redux了。
什么情况下使用

  • 用户使用方式复杂 -- 交互频繁且业务逻辑复杂
  • 多个用户之间可以协作
  • 与服务器大量交互
  • View需要从多个来源获取数据

在多交互多数据源逻辑复杂的情况下,才需要用到Redux;具体情况要根据自身需求确定。

三、Redux组成部分

Redux是由三个重要组成部分协同工作的。包括:actionreducerstore

3.1 Action
  • Action就是一个普通Object。其中type属性是必须包含的,用来区分action或表示action的名字。其余属性可以自行配置。

  • Action是View发出的通知。View与用户发生交互会引起storestate的改变,但是用户不能直接操作state,只能看到或接触到View层,所以需要在操作View的时候通知state发生改变,此时就要用到action。我们可以把action当成是状态存储器store中状态更新的触发器。

const kAction_Add = {
  type: 'Action_Add',
  info: 'Todo_add'
}

如上:Action的名称为Action_Add,跟随该Action携带的信息为info,值为Todo_add,将整个对象定义为常量,命名为kAction_Add

  • View会发送很多种消息,因此就会有很多种Action,或者是同一种Action,但是携带的信息不同。我们不可能都手写成常量,所以就会想到用函数的方式来生成Action,这就是Action Creator。
const kAction_Update = 'kAction_Update';

export function update(info) {
  return {
    type: kAction_Update,
    info
  }
}

const updateAction = update('information');

上面例子中:定义常量kAction_Update代表Action的名称,应用中Action种类比较多的时候可以将常量统一用一个常量JS文件管理,并导出方便引用。声明方法update(info),可能没接触过React或JS的同学比较困惑对象中info是什么意思,这里代表的含义是info: info,前一个info是key值,后一个info是方法传进来的参数。

  • 明白了Action的创建之后,就需要了解Action的发送。store.dispatch()是View发出Action的唯一方式。
import { createStore } from 'redux';
// 这里的 func 是指函数 -- reducer
const store = createStore(func);
store.dispatch({ type: 'kAction_Update', info: 'information'})

结合上面的代码可以写成:

store.dispatch(update('information'));
3.2 Reducer
  • View发送actionstore,引起了state的改变,使得View发生改变。那么action是如何链接到store中状态的,又是如何引起状态改变的?这就用到了reducer。我们可以将reducer看作是actionstore之间的纽带。

  • Reducer是一个函数,它需要两个参数:一个是默认的state;另一个是action。最终经过reducer处理过后会返回一个新的state

import { combineReducers } from 'redux';
import * as Types from '../constants/type';

const initState = {
  info: ''
};

function handleAction(state = initState, action) {
  switch (action.type) {
    case Types.kAction_Update:
      return {
        ...state,
        info: 'new' + action.info
      };
    case Types.kAction_Add:
      return {
        ...state,
        todo: 'new' + action.todo
      };
    default:
      return state;
  }
}

// handleAction 是一个 Reducer 函数,可以有多个类似handleAction这样的 Reducer 函数,用 combineReducers 整合成一个大的 Reducer
export default combineReducers({
  handleAction
});
  • Redux提供combineReducers方法,用来将Reducer拆分。可以定义多个Reducer函数,最终用该方法将它们组合成一个大的Reducer。
  • 由于Reducer是纯函数,函数的返回结果是由actionstate决定的。正是Reducer这个特性决定了函数中不能修改state,只能返回一个全新对象。

Q:什么是纯函数?
A:纯函数是函数式编程的概念,需要满足以下约束条件:
  1. 不得改写参数
  2. 不能调用系统 I/O 的 API
  3. 不能调用Data.now()或Math.random()等不纯的方法,因为每次得到的结果不一样

3.3 Store
  • Store是存储数据的地方,一个应用只能有一个store。我们可以把它看成是浮在所有页面之上的一个数据存储容器,不同的组件和页面都可以从这个容器中拿到需要的数据。
  • Redux提供createStore函数来生成store
import { createStore } from 'redux';
import Reducers from '../reducers/commonReducer';

const commonStore = createStore(Reducers);

会接收一个Reducer作为参数,返回生成的Store对象。

Store有以下职责

  1. store.getState() 方法获取state;
  2. store.dispatch(action) 方法更新state;
  3. store.subscribe(listener) 注册状态监听器;
  4. store.subscribe(listener) 返回的函数注销监听器。
  • Store允许使用 store.subscribe 方法设置监听函数,一旦state发生变化,就自动执行这个函数。
import { createStore } from 'redux';
const store = createStore(reducer);

store.subscribe(listener);

store.subscribe方法返回一个函数,调用这个函数就可以解除监听。

let unsubscribe = store.subscribe(() =>
  console.log(store.getState())
);

unsubscribe();

四、React-Redux介绍

ReactRedux原本是两个相互独立的框架,为了使用起来更高效更灵活,Redux官方针对React封装了一套数据状态管理框架 -- React-Redux

4.1 React-Redux 组件分离思想

React-Redux涉及到容器组件和展示组件分离的思想,在React-Redux中将组件分为 UI组件容器组件

  • UI组件

什么是UI组件?可以理解为只负责页面显示。UI组件有如下特征:

1、只负责UI呈现,不包含任何逻辑处理;
2、没有状态(即不使用this.state);
3、所有数据都由props提供;
4、不使用Redux的API。

  • 容器组件

容器组件包含以下特征:

1、负责数据处理和业务逻辑;
2、包含内部状态;
3、会使用Redux的API。

可能很多人比较困惑:我们自己写的组件里面也有很多业务逻辑和数据处理,难道要都拆开吗?其实不用,React-Redux规定,UI组件由用户提供,容器组件由 React-Redux 自动生成。
我们可以理解为用户自己创建的组件都是UI组件,和React-Redux生成的容器组件是互不干扰的,它能提供组件,使整个应用可以访问到Redux Store

4.2 connect 方法

React-Redux提供connect方法,用于从UI组件生成容器组件;该方法不会改变用户创建的组件,而是返回新的已与 Redux store 连接之后生成的 connect 过的组件。

connect([mapStateToProps], [mapDispatchToProps], [mergeProps], [options])

connect可以有四个参数,但是常用的只有前两个mapStateToPropsmapDispatchToProps

  • mapStateToProps(state, [ownProps]): stateProps

mapStateToProps:从字面意思也可以理解,该函数的作用就是建立从(外部的)state到UI组件的props的映射关系。定义该参数,组件就会监听 Redux Store 的数据变化,只要 Store 中对应数据发生变化,就会调用该函数,函数中的对象会与组件的props合并。如果指定了该回调函数中的第二个参数 ownProps,则该参数的值为传递到组件的 props,而且只要组件接收到新的 props(例如:父组件中参数修改导致当前组件props变动),mapStateToProps 也会被调用。

const mapStateToProps = (state, ownProps) => {
  return {
    count: state.counter.count,
    imageUrl: state.load.imageUrl === ownProps.imageUrl
  }
}
  • mapDispatchToProps(dispatch, [ownProps]): dispatchProps

mapDispatchToProps:是connect的第二个参数,作用是用来建立UI组件和store.dispatch之间的映射。如果传递的参数是一个对象,那么每个定义在该对象中的函数都会被当作Action Creator
函数写法举例:

/**
 * 各个Action可以分开表示
 * 需要用到 redux 中的方法 bindActionCreators 将 dipstch 与自定义action绑定到一起
 * reduceCount 和 increaseCount 为action对应的属性值,使用时
 * 只需要调用 this.props.reduceCount 或 this.props.increaseCount 即可
 */
const mapDispatchToProps = dispatch => ({
  reduceCount: bindActionCreators(countReduceAction, dispatch),
  increaseCount: bindActionCreators(countIncreaseAction, dispatch)
})
/**
 * action统一用 dispatch 发送
 * 使用时,调用 this.props.dispatch(自定义action creator)
 */
const mapDispatchToProps = dispatch => ({
  dispatch
})
/**
 * 将所有action组合成一个大的 action creator 对象
 * actions 为组合后的对象属性key
 * 使用时,调用 this.props.actions.countReduceAction() 或 this.props.actions.countIncreaseAction()
 */
const mapDispatchToProps = dispatch => ({
  actions: bindActionCreators(Object.assign({}, {
    countReduceAction,
    countIncreaseAction
  }, ), dispatch)
})
  • mergeProps(stateProps, dispatchProps, ownProps): props

mergeProps函数会将前两个参数mapStateToPropsmapDispatchToProps的执行结果和组件自身的props传入到该回调函数中,有特殊数据需要处理的可以在该方法里处理,函数的返回结果将会作为props传递到包装过的组件中,一般情况下可以不用传。

  • options

如果指定该参数,可以定制connector的行为。基本不用,想了解的可以去查看redux文档。

4.3 Provider

<Provider store>可以使组件层级中的所有connect方法,都能够链接到Redux Store。一般情况下需要将根组件嵌套在<Provider>中才能使用connect方法。

render() {
    return (
      <Provider store={this.state.store}>
        <SwitchConstruct
          onNavigationStateChange={this.handleNavigationChange}
        />
      </Provider>
    );
  }

五、简单示例

1、首先创建Action Creators,将action的标识统一用一个文件管理

import * as Types from '../../constant/types';


export function countReduceAction(count) {
  return {
    type: Types.kCount_Reduce,
    count
  }
}

export function countIncreaseAction(count) {
  return {
    type: Types.kCount_Increase,
    count
  }
}

2、创建Reducer,注意需要有初始化数据;多个Reducer的情况下可以用combineReducers方法合并成一个大的Reducers。

/**
 * 计数reducer
 */
import * as Types from '../../constant/types';

const countInitReduce = {
  count: -1
}

export default function handleCount(state = countInitReduce, action) {
  switch (action.type) {
    case Types.kCount_Reduce:
      return {
        ...state,
        count: action.count - 1
      };
    case Types.kCount_Increase:
      return {
        ...state,
        count: action.count + 1
      };
    default:
      return state;
  }
}
import { combineReducers } from 'redux';
import handleCount from './countReducer';
import handleLoadImage from './loadImageReducer';

/**
 * reducers组合返回
 */
const reducers = combineReducers({
  countStore:  handleCount,
  loadImageStore: handleLoadImage
});

export default reducers;

3、生成Store,存储全局状态数据

import reducers from '../reducers/baseReducer';
import { createStore } from 'redux';

export function configStore() {
  const store = createStore(reducers);
  return {
    store
  };
}

4、用Provider包裹根组件,并将其connect到组件中,这样在组件中就可以调用action了。

const mapStateToProps = (state) => ({
    count: state.countStore.count,
    imageUrl: state.loadImageStore.imageUrl
});

const mapDispatchToProps = dispatch => ({
  dispatch
})

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

以上就是ReduxReact-Redux的理解和简单使用,有问题的地方欢迎指出,喜欢的话可以点赞关注

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

推荐阅读更多精彩内容