Redux思想OC简单实现

3,716 阅读6分钟

一、Redux设计理念

Redux是将整个应用状态存储到一个地方上称为store,里面保存着一个状态树store tree,组件可以派发(dispatch)行为(action)给store,而不是直接通知其他组件,组件内部通过订阅store中的状态state来刷新自己的视图。

image.png

二、我们是否需要Redux

1.首先明确一点,Redux 是一个有用的架构,但不是非用不可。大多数情况,可以不用它。

  • "如果你不知道是否需要 Redux,那就是不需要它。"
  • "只有遇到 React 实在解决不了的问题,你才需要 Redux 。"

2.简单说,如果你的UI层非常简单,没有很多互动,Redux 就是不必要的,用了反而增加复杂性。下面这些情况,都不需要使用 Redux。

  • 用户的使用方式非常简单
  • 用户之间没有协作
  • 不需要与服务器大量交互
  • 视图层(View)只从单一来源获取数据

3.用户的使用方式复杂,下面这些情况才是 Redux 的适用场景:多交互、多数据源。

  • 不同身份的用户有不同的使用方式(比如普通用户和管理员)
  • 多个用户之间可以协作
  • 与服务器大量交互,或者使用了WebSocket
  • View要从多个来源获取数据

从组件角度看,如果你的应用有以下场景,可以考虑使用 Redux。比如像爱奇艺、优酷、腾讯视频这种复杂播放器。

  • 某个组件的状态,需要共享
  • 某个状态需要在任何地方都可以拿到
  • 一个组件需要改变全局状态
  • 一个组件需要改变另一个组件的状态

总之,不要把 Redux 当作万灵丹,如果你的应用没那么复杂,就没必要用它。

三、Redux三大原则

  • 唯一数据源
  • 保持只读状态
  • 数据改变只能通过纯函数来执行
1、唯一数据源

整个应用的state都被存储到一个状态树里面,并且这个状态树,只存在于唯一的store中

2、保持只读状态

state是只读的,唯一改变state的方法就是触发action,action是一个用于描述以发生时间的普通对象

3、数据改变只能通过纯函数来执行

使用纯函数来执行修改,为了描述action如何改变state的,你需要编写reducers

四、Redux API介绍

1、Store

Store 就是保存数据的地方,你可以把它看成一个容器。整个应用只能有一个 Store。

2、State

Store对象包含所有数据。如果想得到某个时点的数据,就要对 Store 生成快照。这种时点的数据集合,就叫做 State。

3、Action

State 的变化,会导致 View 的变化。但是,用户接触不到 State,只能接触到 View。所以,State 的变化必须是 View 导致的。Action 就是 View 发出的通知,表示 State 应该要发生变化了。

4、Action Creator

View 要发送多少种消息,就会有多少种 Action。如果都手写,会很麻烦。可以定义一个函数来生成 Action,这个函数就叫 Action Creator。

5、store.dispatch()

store.dispatch()是 View 发出 Action 的唯一方法。

6、Reducer

Reducer就是一个纯函数,输入就是oldState以及action,然后输出就是一个newState,注意这里就类似函数式编程,reducer是不应该改动到对象的属性的,就是不应该产生副作用,仅仅是用于计算的感觉。也就是无论执行多少次,结果都一样,无论对象是什么,只要输入的一致,输出就不变。

简单来说,Reducer 是一个函数,它接受 Action 和当前 State 作为参数,返回一个新的 State。

五、OC实现

根据上面的分析,我们就开始设计我们的类了。为了能够让这个redux更通用,所以我们在设计的时候最好可以抽象一下, 具体结构如下:

image.png

1. CCAction
@protocol CCAction <NSObject>

/** action,每个行为有唯一的标识 */
@property (nonatomic, copy, readonly) NSString *identifier;

/** 为action添加的额外信息 */
@property (nonatomic, strong, readonly, nullable) id payload;

@end

2. CCReducer
#import <Foundation/Foundation.h>
#import "CCState.h"
#import "CCAction.h"

typedef void (^RDXReduceBlock)(__kindof id <CCState> state, __kindof id <CCAction> action);

@protocol CCReducer <NSObject>

/**
 * 返回 reducer blocks 数组.
 *
 * @return RDXReduceBlock 数组
 */
+ (NSArray <RDXReduceBlock> *)reducers;

@end

3. CCState
/**
 实现copy协议,为了数据存储
 [NSKeyedUnarchiver unarchiveObjectWithData:[NSKeyedArchiver archivedDataWithRootObject:state]]
 */

@protocol CCState <NSCoding>

@end

4. CCStore
#import <Foundation/Foundation.h>
#import "CCState.h"
#import "CCAction.h"
#import "CCState.h"
#import "CCReducer.h"

FOUNDATION_EXPORT NSNotificationName const kCCStateDidChangeNotification;

typedef void (^CCStoreNotifationCallback)(void);

@interface CCStore : NSObject

- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)new NS_UNAVAILABLE;

/**
 * 创建一个Store
 *
 * @param reducers reducers 数组
 * @param initialState 初始化状态
 * @return Store
 */
- (instancetype)initWithReducers:(NSArray <RDXReduceBlock> *)reducers state:(id <CCState> )initialState NS_DESIGNATED_INITIALIZER;

/**
 * 要求接受者发送action并立即返回
 * 接受者通过当前的state和action返回给所有的负责更新State的reduce blocks
 * 所有接收到reduce blocks的都会被触发,同时发送通知 CCStateDidChangeNotification
 * @param action The action object to be dispatched.
 */
- (void)dispatchAction:(id <CCAction> )action;

/**
 * 接受者当前状态
 *
 * @return 当前状态,这个状态经过深度copy
 */
- (id <CCState> )currentState;

@end

六、实际接入

基于上面我们抽象出来的框架,我们来用实际的Demo演示一下如何使用,这个Demo就是一个简单的加1和减1的功能。

image.png

对应的代码结构

image.png

接下来我们仔细介绍一些,这些类都干了什么?

1. Action

.h 头文件内容

#import <Foundation/Foundation.h>
#import "CCAction.h"

extern NSString * _Nullable const kActionIdentifierIncreaseCount;
extern NSString * _Nullable const kActionIdentifierDecreaseCount;

@interface Action : NSObject <CCAction>

- (instancetype _Nullable )init NS_UNAVAILABLE;
+ (instancetype _Nullable )new NS_UNAVAILABLE;

/**
 * 创建一个action
 *
 * @param identifier 标识
 * @param payload    额外信息,有效负载信息
 * @return Newly created action object.
 */
+ (instancetype _Nullable )actionWithIdentifier:(NSString *_Nullable)identifier payload:(nullable id)payload;

/**
* 创建一个action
*
* @param identifier 标识
* @param payload    额外信息,负载
* @return Newly created action object.
*/
- (instancetype _Nullable )initWithActionIdentifier:(NSString *_Nullable)identifier payload:(nullable id)payload NS_DESIGNATED_INITIALIZER;

@end

对应的.m 文件

#import "Action.h"

NSString * const kActionIdentifierIncreaseCount = @"ActionIdentifierIncreaseCount";
NSString * const kActionIdentifierDecreaseCount = @"ActionIdentifierDecreaseCount";

@interface Action ()

/** 标识 */
@property (nonatomic, copy, readwrite) NSString *identifier;
/** 有效负载信息 */
@property (nonatomic, strong, readwrite) id payload;

@end

@implementation Action

+ (instancetype)actionWithIdentifier:(NSString *)identifier payload:(nullable id)payload {
    return [[self alloc] initWithActionIdentifier:identifier payload:payload];
}

- (instancetype)initWithActionIdentifier:(NSString *)identifier payload:(id)payload {
    if (self = [super init]) {
        _identifier = [identifier copy];
        _payload = payload;
    }
    return self;
}

@end

2. State

.h 头文件内容

#import <Foundation/Foundation.h>
#import "CCState.h"

@interface State : NSObject<CCState>

@property (nonatomic, assign) NSInteger count;

@end

对应.m 实现

#import "State.h"

@implementation State

- (instancetype)init {
    if (self = [super init]) {
        _count = 0;
    }
    return self;
}

- (instancetype)initWithCoder:(NSCoder *)aDecoder {
    if (self = [self init]) {
        _count = [aDecoder decodeIntegerForKey:@"count"];
    }
    return self;
}

- (void)encodeWithCoder:(NSCoder *)aCoder {
    [aCoder encodeInteger:self.count forKey:@"count"];
}

@end

3. Reducer

.h 文件内容

#import <Foundation/Foundation.h>
#import "CCReducer.h"

@interface Reducer : NSObject<CCReducer>

@end

对应.m 文件内容

#import "Reducer.h"
#import "Action.h"
#import "State.h"

@implementation Reducer

+ (RDXReduceBlock)increaseReducer {
    
    RDXReduceBlock reducer = ^(State *state, Action *action) {
        if ([action.identifier isEqualToString:kActionIdentifierIncreaseCount]) {
            state.count++;
        }
    };
    return reducer;
}

+ (RDXReduceBlock)decreaseReducer {
    RDXReduceBlock reducer = ^(State *state, Action *action) {
        if ([action.identifier isEqualToString:kActionIdentifierDecreaseCount]) {
            state.count--;
        }
    };
    return reducer;
}

+ (NSArray <RDXReduceBlock> *)reducers {
    return @[
        [self increaseReducer],
        [self decreaseReducer]
    ];
}

@end

4. Store

.h 文件内容

#import <Foundation/Foundation.h>
#import "CCStore.h"

@interface Store : CCStore

/** 单例 */
+ (instancetype)sharedStore;

@end

.m 文件内容

#import "Store.h"
#import "Reducer.h"
#import "State.h"

@implementation Store

+ (instancetype)sharedStore {
    static id _sharedStore = nil;
    static dispatch_once_t onceToken;
    dispatch_once(&onceToken, ^{
        _sharedStore = [[self alloc] init];
    });

    return _sharedStore;
}

- (instancetype)init {
    NSArray *reducers = [Reducer reducers];
    State *initialState = [[State alloc] init];
    self = [super initWithReducers:reducers state:initialState];
    return self;
}

@end

5. 最后我们的使用姿势
@interface ViewController ()

/** 增加 */
@property (nonatomic, strong) UIButton *increaseButton;
/** 减少 */
@property (nonatomic, strong) UIButton *decreaseButton;
/** 显示 */
@property (nonatomic, strong) UILabel *textLabel;

@end

@implementation ViewController

- (void)viewDidLoad {
    [super viewDidLoad];

    self.view.backgroundColor = [UIColor whiteColor];
    
    self.increaseButton = [UIButton buttonWithType:UIButtonTypeCustom];
    self.increaseButton.frame = CGRectMake(30, 300, 100, 30);
    [self.increaseButton setTitle:@"增加" forState:UIControlStateNormal];
    [self.increaseButton setBackgroundColor:[UIColor orangeColor]];
    [self.increaseButton addTarget:self action:@selector(increaseButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.increaseButton];
    
    self.decreaseButton = [UIButton buttonWithType:UIButtonTypeCustom];
    self.decreaseButton.frame = CGRectMake(230, 300, 100, 30);
    [self.decreaseButton setTitle:@"减少" forState:UIControlStateNormal];
    [self.decreaseButton setBackgroundColor:[UIColor orangeColor]];
    [self.decreaseButton addTarget:self action:@selector(decreaseButtonClick:) forControlEvents:UIControlEventTouchUpInside];
    [self.view addSubview:self.decreaseButton];
    
    self.textLabel = [[UILabel alloc] initWithFrame:CGRectMake(30, 400, self.view.frame.size.width - 60, 30)];
    self.textLabel.textColor = [UIColor orangeColor];
    self.textLabel.textAlignment = NSTextAlignmentCenter;
    self.textLabel.text = @"当前是0";
    [self.view addSubview:self.textLabel];
    
    [[NSNotificationCenter defaultCenter] addObserver:self
                                             selector:@selector(updateUI:)
                                                 name:kCCStateDidChangeNotification
                                               object:nil];
}

- (void)increaseButtonClick:(UIButton *)button {
    Action *action = [[Action alloc] initWithActionIdentifier:kActionIdentifierIncreaseCount payload:nil];
    [[Store sharedStore] dispatchAction:action];
}

- (void)decreaseButtonClick:(UIButton *)button  {
    Action *action = [[Action alloc] initWithActionIdentifier:kActionIdentifierDecreaseCount payload:nil];
    [[Store sharedStore] dispatchAction:action];
}

- (void)updateUI:(NSNotification *)note {
    State *state = (State *)[[Store sharedStore] currentState];
    self.textLabel.text = [NSString stringWithFormat:@"当前是%ld", state.count];
}

- (void)dealloc {
    [[NSNotificationCenter defaultCenter] removeObserver:self];
}

7. 传送门

如果上面大家看完还不是很明白,可以下载demo来仔细研究一下 OC实现Redux思想