I. Redux design concept

Redux stores the entire application state in a place called a Store, which holds a state tree. Components can dispatch actions to the Store instead of notifting other components directly. The component internally refreshes its own view through the state state in the subscription store.

Two, do we need Redux

1. To be clear, Redux is a useful architecture, but you don’t have to use it. In most cases, you don’t have to use it.

  • “If you don’t know if you need Redux, you don’t need it.”
  • “You only need Redux when React isn’t really a solution.”

2. Simply put, if your UI layer is very simple and doesn’t have a lot of interaction, Redux is unnecessary and adds complexity. In these cases, Redux is not required.

  • The way users use it is very simple
  • There is no collaboration between users
  • You don’t have to interact a lot with the server
  • The View layer only gets data from a single source

3. Redux applies to multiple interactions and multiple data sources in complex ways.

  • Users of different identities have different usage modes (for example, ordinary users and administrators)
  • Multiple users can collaborate
  • A lot of interaction with the server, or using websockets
  • The View gets data from multiple sources

From a component perspective, consider using Redux if your application has the following scenarios. Sophisticated players like IQiyi, Youku and Tencent Video.

  • The state of a component that needs to be shared
  • A state needs to be available anywhere
  • A component needs to change global state
  • One component needs to change the state of another component

In short, don’t think of Redux as a panacea. If your application isn’t that complex, you don’t need it.

Three, Redux three principles

  • Unique data source
  • Keep it read-only
  • Data changes can only be performed by pure functions
1. Unique data source

The state of the entire application is stored in a state tree, and this state tree exists only in a single store

2. Keep the read-only state

State is read-only, and the only way to change state is to trigger an action, which is a common object that describes when it happened

3. Data changes can only be performed by pure functions

To use pure functions to make changes, you need to write reducers to describe how actions change state

4. Redux API introduction

1, the Store

A Store is a place where data is stored, you can think of it as a container. There can only be one Store for the entire app.

2, the State

The Store object contains all the data. If you want data at a point in time, take a snapshot of the Store. This collection of data at this point in time is called State.

3, the Action

If the State changes, the View changes. However, the user does not touch State, only View. Therefore, the change in State must be caused by the View. An Action is a notification from the View that the State should change.

4, the Action of Creator

There are as many actions as there are messages that the View wants to send. If they were all handwritten, it would be troublesome. You can define a function to generate Action, called Action Creator.

5, store. The dispatch ()

Store.dispatch () is the only way a View can issue an Action.

6, Reducer

Reducer is a pure function, the input is oldState and action, and the output is newState. Note that this is similar to functional programming, Reducer should not change the attributes of the object, that is, there should be no side effects, only for the feeling of calculation. That is, no matter how many times you execute it, the result is the same, no matter what the object is, as long as the input is consistent, the output is the same.

Simply put, Reducer is a function that takes Action and the current State as parameters and returns a new State.

5. OC implementation

Based on the above analysis, we began to design our class. In order to make the redux more versatile, it is best to abstract it as follows:

1. CCAction
@protocol CCAction <NSObject> /** action, each behavior has a unique identifier */ @property (nonatomic, copy, readonly) NSString *identifier; / / @property (nonatomic, strong, readOnly, nullable) id payload; @endCopy the code
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> /** * Return Reducer Blocks ** @return RDXReduceBlock */ + (NSArray <RDXReduceBlock> *)reducers; @endCopy the code
3. CCState
/** Implement copy protocol, In order to data storage [NSKeyedUnarchiver unarchiveObjectWithData: [NSKeyedArchiver archivedDataWithRootObject: state]] * / @ protocol CCState <NSCoding> @endCopy the code
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; /** * Create a Store ** @Param Reducers Reducers array * @Param initialState Initialization state * @return Store */ - (instancetype)initWithReducers:(NSArray <RDXReduceBlock> *)reducers state:(id <CCState> )initialState NS_DESIGNATED_INITIALIZER; /** * Request that the recipient send an action and return it immediately * The recipient sends an action back to all reduce blocks responsible for updating state with the current state and action * All reduce blocks that receive reduce blocks will be triggered. At The same time send a notification CCStateDidChangeNotification * @ param action The action object to be dispatched. * / - (void) dispatchAction: (id <CCAction> )action; /** * @return currentState */ - (id <CCState>)currentState; @endCopy the code

6. Actual access

Based on the framework we abstracted above, let’s use a real Demo to show how to use it. This Demo is a simple add and subtract 1 function.

Corresponding code structure

So let’s talk a little bit more about, what do these classes do?

1. Action

H Header file contents

#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; /** * Create an action ** @param identifier * @param payload @return Newly created Action Object. */ + (InstanceType _Nullable)actionWithIdentifier (NSString) *_Nullable)identifier payload:(nullable id)payload; /** * Create an action ** @param identifier * @param payload Load * @ return Newly created the action object. * / - (instancetype _Nullable) initWithActionIdentifier: (nsstrings *_Nullable)identifier payload:(nullable id)payload NS_DESIGNATED_INITIALIZER; @endCopy the code

The corresponding.m file

#import "Action.h" NSString * const kActionIdentifierIncreaseCount = @"ActionIdentifierIncreaseCount"; NSString * const kActionIdentifierDecreaseCount = @"ActionIdentifierDecreaseCount"; @interface Action () /** identifier */ @property (nonatomic, copy, readWrite) NSString *identifier; /** payload information */ @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; } @endCopy the code
2. State

H Header file contents

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

@interface State : NSObject<CCState>

@property (nonatomic, assign) NSInteger count;

@end

Copy the code

Corresponding to the.m implementation

#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

Copy the code
3. Reducer

H File content

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

@interface Reducer : NSObject<CCReducer>

@end

Copy the code

Corresponding to the contents of the. M file

#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] ]; } @endCopy the code
4. Store

H File content

#import <Foundation/ foundation. h> #import "ccstore. h" @interface Store: CCStore /** singleton */ + (instanceType)sharedStore; @endCopy the code

. M File content

#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

Copy the code
5. Finally, we use posture
@interface ViewController () /** increment */ @property (nonatomic, strong) UIButton *increaseButton; /** reduce */ @property (nonatomic, strong) UIButton *decreaseButton; /** display */ @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); [the self increaseButton setTitle: @ "increase" 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); [the self decreaseButton setTitle: @ "reduce" 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 = @" currently 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:@" currently %ld", state.count]; self.textLabel.text = [NSString stringWithFormat:@" currently %ld", state.count]; } - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; }Copy the code

7. The portal

If you are still confused by the above, download the demo to examine the idea of OC implementing Redux in detail