Unidirectional Data Flow in C# - Inspired by Redux
There is a library in JavaScript called Redux. If you have ever developed a Single Page Application with React, you may have used it. For more information on Redux, please read the official documentation.
ReduxSharp is a port of Redux to C#.
First, install Nuget. Then, install ReduxSharp from the package manager console:
PM> Install-Package ReduxSharp
Define a class that represents a state of the app.
namespace HelloWorld
{
public class AppState
{
public AppState(int count) => Count = count;
public int Count { get; }
}
}
Define actions. Actions are payloads of information that send data from your application to your store.
namespace HelloWorld
{
public readonly struct Increment {}
public readonly struct Decrement {}
}
Define a reducer.
A reducer need to implement the interface IReducer<TState>
.
It describes how an action transforms the state into the next state.
using System;
using ReduxSharp;
namespace HelloWorld
{
public class AppReducer : IReducer<AppState>
{
public AppState Invoke<TAction>(AppState state, TAction action)
{
switch (action)
{
case Increment _:
return new AppState(state.Count + 1);
case Decrement _:
return new AppState(state.Count - 1);
default:
return state;
}
}
}
}
Create an instance of Store<TState>
.
The Store<TState>
is the class that bring actions and reducer together.
The store has the following responsibilities:
- Holds application state of type TState.
- Allows state to be update via
Dispatch<TAction>(TAction action)
. - Registers listeners via
Subscribe(IObserver observer)
.TheStore<TState>
class implements IObservable.
The Store<TState>
take an initial state, of type TState, and a reducer.
using System;
using ReduxSharp;
namespace HelloWorld
{
class Program : IObserver<AppState>
{
static void Main(string[] args)
{
var store = new Store<AppState>(
new AppReducer(),
new AppState(0));
var p = new Program();
using (store.Subscribe(p))
{
store.Dispatch(new Increment()); // => 1
store.Dispatch(new Increment()); // => 2
store.Dispatch(new Decrement()); // => 1
store.Dispatch(new Increment()); // => 2
}
}
public void OnNext(AppState value) =>
Console.WriteLine(value.Count);
public void OnCompleted() { }
public void OnError(Exception error) { }
}
}
ReduxSharp supports middlewares.
You can insert processing before and after IReducer<TState>.Invoke<TAction>(TState, TAction)
is called.
using Newtonsoft.Json;
using ReduxSharp;
public class ConsoleLoggingMiddleware<TState> : IMiddleware<TState>
{
public void Invoke<TAction>(IStore<TState> store, IDispatcher next, TAction action)
{
Console.WriteLine(JsonConvert.SerializeObject(store.State));
next.Invoke(action);
Console.WriteLine(JsonConvert.SerializeObject(store.State));
}
}