用 redux 有一段时间了, 感觉还是有必要把其相关的知识点系统的总结一下的, 毕竟好记性不如烂笔头. 上篇博客更新了关于《ES6 中的迭代器, Generator 函数以及 Generator 函数的异步操作》的内容, 该内容时 saga 的基础, 稍后会总结 saga 相关知识点. 循序渐进, 本篇博客主要总结的是 Redux 相关的内容, 然后下篇博客打算总结一下 react-redux, 以及 redux-thunk,redux-saga 中间件.
一, Redux 与 iOS 中的 Notification 的比较
Redux 的功能和作用就是让 State 管理更为集中, 因为在 redux 中所有的状态都是存储在 Store 中的, 而在页面的各个模块中都可以去访问和修改 Store 中存储的状态值. 从这一点来看, redux 可以很好的解决一个页面中多个模块间的状态共享的问题.
Redux 这框架理解起来是比较简单的, 这个框架本身也是比较小的, 涉及的 API 也非常少. 虽然小, 但小而精. 使用起来还是满顺手的, 大道至简. 下方是 Redux 中的几个关键的词及对应的功能, 理解完下方的几个关键词, Redux 这个框架也就大概了解了.
Store : 从字面意思看, Store 是存储, 储存的意思, 在 Redux 中, 把相关的状态存储在了 Store 中, 在 Redux 中 Store 可以看做是一个单例对象. 并且 Store 中提供了一些 API 来操作这些状态, 如下所示:
getState : 该方法用来获取 Store 中当前存储的状态值.
subscribe(listener 回调方法): 用来监听 Store 中状态值的改变, 状态值改变后会执行相关回调方法.
dispatch (action) : 该方法用来修改 Store 中存储的状态值, 而 Action 就是一个普通的对象, 其中可以携带一些修改特定状态时的一些信息.
Action: 上面也说了, 而 Action 就是一个普通的对象, 其中可以携带一些修改特定状态时的一些信息, 被用来作为 dispatch() 方法的参数的. 起到了媒介的作用, Action 本身会携带一些信息, 便于状态的修改.
Reducer: Reducer 本质上是一个 方法集合的称呼, 而这些方法的入参是 当前的 State 和 Action, 出参是被修改后的新的 State 对象, 也就是说 dispatch 一个 Action 会执行一个 Reducer. 而 Reducer 对应方法, 会根据 Action 携带的信息来修改 State 对象, 并把修改后的 State 对象返回出去. 当然返回这新的 State 会更新到 Store 中, 从而会触发一系列的监听操作.
Redux 的工作模式虽然是管理状态的, 但是使用上个人感觉更想通知. 与 iOS 中的 Notification 工作方式即为相似, 下方做了一些简单的类比. 下方简单的画了一个类比的图, 可以从下往上看, 解释如下:
通知中心: 最下方是通知中心, 对应着 iOS 的 NotificationCenter, 主要用来注册, 派发及移除通知的, 所以的通知都会经过 NotificationCenter 的管理. 在 Redux 中, 这个 Store 就扮演着 这个 NotificationCenter 的角色, 用来管理所有的状态. 不同的时, Store 中会存储各种状态.
发送通知: 如果要修改状态的值的话, 得调用 Store 中提供的 dispatch(事件派发) 方法来修改相关的状态, 这个就好像 iOS 中发送通知的 Post 方法.
注册监听: 而 Store 中的 subscribe 这个监听状态改变的方法, 就类似于 Notification 中的 register 方法, 只有添加完监听的相关对象才能收到状态被修改的通知.
通知对象: Store 中的 dispatch() 方法的参数 Action, 就类似于 Notification 对象, 用来携带通知或者状态修改的信息.
执行方法: 而 redux 中的 Reducer 就类似于执行通知的 Selector, 用来修改状态的.
二, 通过加减法示例来看 Redux 的使用方式
下方通过一个简单的加减法程序来看一下 Redux 的使用方式. 之前在介绍 iOS 中的响应式框架 ReactiveCocoa 时写过类似的 Demo, 只不过今天我们用 Redux 来实现一下.
demo 比较简单, 就是两个加减法, 输入的时候自动的修改计算的结果值. 下方我们就来简单的看一下 RN 中如何使用 Redux 来实现该功能.
1, 创建 Store
首先创建 Store,redux 专门提供了一个创建 store 的方法 createStore , 调用 createStore 时, 我们需要把修改 State 的 Reducer 方法传进去进行关联. 下方的 calculateReducer 是自定义的一个修改 State 的方法, 稍后会介绍. 下方代码比较简单, 就是创建了一个 Store, 并将该对象导了出去.
2, 创建 Action
创建 Store 后, 接下来我们来创建对应的 Action, 下方代码就是对应的 action 文件中的内容. 首先创建了一个 CountActionType 的对象, 功能类似于枚举, 其中 "ADD" 代表加法类型,"DESC" 代表减法类型. 因为该示例中是在一个 Reducer 中处理的两个 Action, 所以得用 CountActionType 类型来判断派发的是哪个 Action, 然后做对应的操作.
然后创建了一个 addTowNumbers 方法, 该方法接收了一个参数, 然后返回一个 Action 对象, 其中 Action 对象的类型就是 ADD. 而下方的 descTowNumbers 方法返回的也是一个 Action, 该 Action 对应的是减法操作. 稍后我们会使用到该 Action.
3, 创建 Reducer
下方的 calculateReducer 方法就是我们创建的 Reducer, 该方法接收两个参数, 一个是 State 对象, 一个是 Action 对象. 我们给 State 对象赋了一个默认值, 这个默认值中有两个值, 一个是表示加法结果的 addResult, 另一个是表示减法结果的 descResult.
Action 对象中的 payload 对象中有两个值, 及 firstNumberhe 和 secondNumber, 表示输入的两个值. 而在 Reducer 中通过 Action 的 Type 字段来判断是做加法操作还是减法操作. 如果是 Add 则是加法操作, 将 payload 中的两个值相加, 然后将结果赋值给 state 中的 addResult. 如果是 Desc 的话, 与 Add 类似, 只不过做的是减法操作.
在该 Reducer 方法中, 返回的是一个计算后端新的 State.State 被修改后, 可以通过 Store 中的 subscribe 的方法进行监听该状态的改变.
4,AddTestView 的实现
定义好 Store,Action,Reducer, 接下来我们就开始定义可操作的视图了. 下方的 AddTestView 就是上面两个计算加减法的控件. 下方是具体实现的说明:
在 AddTestView 中的构造方法中, 我们调用了 store 对象中的 subscribe 方法, 传入了一个回调方法, 来对 Store 中存储的状态进行监听, 然后获取 state 中最新的状态, 然后赋值给组件对应的 State 对象.
第二段核心的代码则是 dispathAction 了, 在输入框变化后, 会根据是 Add 还是 Desc 调用下方的 dispatchAction 方法. 如果是 Add, 就会调用 addTowNumber 方法创建一个 加法动作对应的 Action. 如果是减法操作的话, 则会调用 descTowNumber() 方法创建一个减法对应的 Action 对象. 然后把创建好的对象, 通过 store.dispatch(action) 方法派发出去.
store 收到 Action 后就会执行对应的 Reducer 方法, 然后去跟进 Action 提供的信息修改 Store 中存储的 State 值. 当 State 值被修改后, 就会执行 subscriber 对应的回调方法获取最新的结果值, 并赋值给组件内部的 State 对象进行展示.
下方 AddTestView 的全部代码.
- // 仅仅使用 redux
- import React, { Component } from 'react';
- import { Action } from 'redux';
- import {Text, TouchableOpacity, View, StyleSheet, TextInput} from 'react-native';
- import { store } from './store';
- import {addTowNumbers, descTowNumbers, CountActionType} from './action';
- const {
- DESC,
- ADD
- } = CountActionType;
- type State = {
- addResult: number,
- descResult: number
- };
- const styles = StyleSheet.create({
- textInput: {
- width: 60,
- borderRadius: 4,
- borderWidth: 0.5,
- borderColor: 'gray'
- },
- tipText: {
- }
- });
- export default class AddTestView extends Component<null, State> {
- addFirstNumber: string = '0';
- addSecondNumber: string = '0';
- descFirstNumber: string = '0';
- descSecondNumber: string = '0';
- constructor (props: any) {
- super(props);
- this.state = {
- addResult: 0,
- descResult: 0
- };
- store.subscribe(() => {
- const {
- addResult,
- descResult
- } = store.getState();
- this.setState({ addResult, descResult });
- });
- }
- firstTextChange = (type) => (text) => {
- if (type === CountActionType.ADD) {
- this.addFirstNumber = text;
- this.dispathAddAction();
- } else {
- this.descFirstNumber = text;
- this.dispathDescAction();
- }
- }
- secondTextChange = (type) => (text) => {
- if (type === CountActionType.ADD) {
- this.addSecondNumber = text;
- this.dispathAddAction();
- } else {
- this.descSecondNumber = text;
- this.dispathDescAction();
- }
- }
- dispathAddAction = () => {
- const action = addTowNumbers({firstNumber: this.addFirstNumber, secondNumber: this.addSecondNumber});
- store.dispatch(action);
- }
- dispathDescAction = () => {
- const action = descTowNumbers({firstNumber: this.descFirstNumber, secondNumber: this.descSecondNumber});
- store.dispatch(action);
- }
- calculate = (type) => {
- const calculateText = type === CountActionType.ADD ? '+' : '-';
- const result = type === CountActionType.ADD ? this.state.addResult : this.state.descResult;
- return (
- <View style={{flexDirection: 'row'}}>
- <TextInput style={styles.textInput} defaultValue={'0'} onChangeText = {this.firstTextChange(type)}/>
- <Text> {calculateText} </Text>
- <TextInput style={styles.textInput} defaultValue={'0'} onChangeText = {this.secondTextChange(type)}/>
- <Text> = </Text>
- <Text>{result}</Text>
- </View>
- );
- }
- render () {
- return (
- <View style={{ justifyContent: 'center', alignItems: 'center' }}>
- {this.calculate(CountActionType.ADD)}
- {this.calculate(CountActionType.DESC)}
- </View>
- );
- }
- }
- View Code
5, 总结
介绍完相关的 Demo, 我们可以总结一些具体的实现流程. 上述各个部分的执行过程是比较简单的, 下方是具体的总结:
Component 也就是下边的 AddTestView 是不会直接调用 Reducer 方法来修改状态的, 而是像 Store 通过 Dispatch 来派发 Action 的方式向 Store 下发修改 State 的命令.
Store 在收到 Component 派发的 Action 后会调用对应的 Reducer.
Reducer 则根据提供的 Action 信息来修改对应的 State 的值, 并返回给 Store, 更新.
Component 最终通过 Subscribe 的方式接收到更新后的 State, 当然派发 Action 的 Component 与 Subscriber 对应状态的 Component 大部分情况下不是一个.
上面是根据上述示例来画的简图, 下方我们可以脱离上述 demo, 整理了一个图. 从下图中不难看出, 平时在开发时, Component 一般是有多个的, 而 Store 只有一个, 这些 Component 都像 Store 派发 Action 修改对应的状态, 并且可以通过 Subscriber 来监听对应状态值的改变.
而 Reducer 也可以是多个, 建议将 Reducer 按照修改状态的类型或者相关的业务逻辑进行拆分, 拆分成多个业务模块. 修改不同的状态时, 会调用不同的 Reducer.
上述我们是声明定义了一个 Reducer , 如果修改 State 的东西都写在一个方法里, 难免会有些难于维护. 所以一般会对 Reducer 进行拆分, 下方是对上述 Reducer 拆分后的代码. 当然运行效果与之前的是一样的, 下方也是推荐用法.
虽然该 Demo, 使用 Redux 实现会比较麻烦, 使用组件内部的 State 完全可以实现, 因为是为了窥探 Redux 的使用方式, 所以我们就用 Redux 实现了该 demo. 但是如果是跨组件的数据交流, 该方式就比较合适了.
本篇博客就先到这儿吧, 虽然本篇博客介绍了 Redux, 但是在开发中很少直接使用, 一般会结合着其他框架及中间件使用. 之前还积累了一些 react-redux, 以及 redux-thunk,redux-saga 的东西, 下篇博客把 react-redux 相关的东西在总结一下, 做个记录也便于自己后期翻阅. 最后附上 redux 的文档链接, 有啥问题可翻阅 https://www.redux.org.cn/.
来源: https://www.cnblogs.com/ludashi/p/10996209.html