![]() |
VOOZH | about |
dotnet add package ReduxDotnet --version 0.2.0
NuGet\Install-Package ReduxDotnet -Version 0.2.0
<PackageReference Include="ReduxDotnet" Version="0.2.0" />
<PackageVersion Include="ReduxDotnet" Version="0.2.0" />Directory.Packages.props
<PackageReference Include="ReduxDotnet" />Project file
paket add ReduxDotnet --version 0.2.0
#r "nuget: ReduxDotnet, 0.2.0"
#:package ReduxDotnet@0.2.0
#addin nuget:?package=ReduxDotnet&version=0.2.0Install as a Cake Addin
#tool nuget:?package=ReduxDotnet&version=0.2.0Install as a Cake Tool
This is a just simple but enough implementation Redux for .NET 6.
https://www.nuget.org/packages/ReduxDotnet
Create state, actions, reducers and effects.
// Define app state
record AppState(int Count);
// Define actions
record IncrementAction();
record DecrementAction();
// Define reducer
class Reducers :
IReducer<AppState, IncrementAction>,
IReducer<AppState, DecrementAction>
{
public AppState Invoke(AppState store, IncrementAction action) =>
store with { Count = store.Count + 1 };
public AppState Invoke(AppState store, DecrementAction action) =>
store with { Count = store.Count - 1 };
}
// Define effects (If you want to use async operation)
class Effects
{
public EffectDelegate<AppState> IncrementLater() => async (d) =>
{
await Task.Delay(2000);
d.Dispatch(new IncrementAction());
};
}
Let's use those on your app. ReduxDotnet is designed work with Microsoft.Extensions.DependencyInjection.
using Microsoft.Extensions.DependencyInjection;
using Reactive.Bindings;
using ReduxDotnet;
var services = new ServiceCollection();
// init ReduxDotnet with initial status
services.AddReduxDotnet<AppState>(new AppState(0));
// Add reducers and effects
services.AddReducer<AppState, Reducers>();
services.AddSingleton<Effects>();
var provider = services.BuildServiceProvider();
// AppStore is IReactiveProperty<AppState>.
var store = provider.GetRequiredService<IReactiveProperty<AppState>>();
store.Subscribe(x =>
Console.WriteLine($"{DateTime.Now:yyyy-MM-dd HH:mm:ss}: Status was changed {x}."));
// Dispatcher and Effects
var dispatcher = provider.GetRequiredService<IDispatcher<AppState>>();
var effects = provider.GetRequiredService<Effects>();
// Dispatch actions
dispatcher.Dispatch(new IncrementAction());
dispatcher.Dispatch(new IncrementAction());
dispatcher.Dispatch(new DecrementAction());
// Async operation
await dispatcher.DispatchAsync(effects.IncrementLater());
The outputs:
2022-04-22 22:13:05: Status was changed AppState { Count = 0 }.
2022-04-22 22:13:05: Status was changed AppState { Count = 1 }.
2022-04-22 22:13:05: Status was changed AppState { Count = 2 }.
2022-04-22 22:13:05: Status was changed AppState { Count = 1 }.
2022-04-22 22:13:07: Status was changed AppState { Count = 2 }.
Create your app's state. On .NET 6, you can use record or record struct like below.
namespace ReduxDotnetSample.Store;
public record AppState(int Count);
Actions are just Plain C# class or struct. You can use record or record struct for Actions too.
// IncrementAction.cs
namespace ReduxDotnetSample.Actions;
public record IncrementAction(int Amount);
// DecrementAction.cs
namespace ReduxDotnetSample.Actions;
public record DecrementAction(int Amount);
Reducer is a class what implements IReducer<TStore, TAction> interface.
The type argument TStore is application's state class you created. And TAction is an action type you want to handle on this reducer class.
You can also implements multiple IReducer<TStore, TAction> to a class like below.
using ReduxDotnet;
using ReduxDotnetSample.Actions;
using ReduxDotnetSample.Store;
namespace ReduxDotnetSample.Reducers;
public class AppReducers :
IReducer<AppState, IncrementAction>,
IReducer<AppState, DecrementAction>
{
public AppState Invoke(AppState store, IncrementAction action) =>
store with { Count = store.Count + action.Amount };
public AppState Invoke(AppState store, DecrementAction action) =>
store with { Count = store.Count - action.Amount };
}
If you want to handle async operations, then you can do that using effects.
Effects are just methods returning EffectDelegate<TStore>.
EffectDelegate<TStore> is defined like below+:
namespace ReduxDotnet;
public delegate ValueTask EffectDelegate<TStore>(IDispatcher<TStore> dispatcher, Func<TStore> getStore);
Effect to increment Count property of AppState after 2 seconds likes below:
using ReduxDotnet;
using ReduxDotnetSample.Actions;
using ReduxDotnetSample.Store;
namespace ReduxDotnetSample.Effects;
public class AppEffects
{
public EffectDelegate<AppState> IncrementAsync(int amount) => async (dispatcher) =>
{
await Task.Delay(2000);
dispatcher.Dispatch(new IncrementAction(amount));
};
}
All implementations were finished! The last step is that registering all to IServiceCollection to use those on your app.
// Register IReactiveProperty<AppState> and IDispatcher<AppState> as singleton.
builder.Services.AddReduxDotnet(new AppState(0));
// Register Reducers for Dispatcher.
builder.Services.AddReducer<AppState, AppReducers>();
// Effects are plain C# classes, so you can register to IServiceCollection same as other classes.
builder.Services.AddSingleton<AppEffects>();
A razor file of Counter is same as no Redux one.
@page "/counter"
<PageTitle>Counter</PageTitle>
<h1>Counter</h1>
<div>
<label for="amount">Amount</label>
<input id="amount" type="text" @bind="Amount" />
</div>
<p role="status">Current count: @CurrentCount</p>
<button class="btn btn-primary" @onclick="IncrementCount">Increment</button>
<button class="btn btn-primary" @onclick="DecrementCount">Decrement</button>
<button class="btn btn-primary" @onclick="IncrementCountAfterTwoSecondsAsync">Increment (Async)</button>
Counter.razor.cs is different as typical one.
The AppState is used as IReactiveProperty<AppState>.
And you can see IDispatcher<AppState>. This is a dispatcher class for Redux.
And then AppEffects are there for async operation.
using Microsoft.AspNetCore.Components;
using Reactive.Bindings;
using Reactive.Bindings.Extensions;
using ReduxDotnet;
using ReduxDotnetSample.Actions;
using ReduxDotnetSample.Effects;
using ReduxDotnetSample.Store;
using System.ComponentModel.DataAnnotations;
using System.Reactive;
using System.Reactive.Disposables;
using System.Reactive.Linq;
namespace ReduxDotnetSample.Pages;
public partial class Counter : IDisposable
{
private readonly CompositeDisposable _disposables = new();
[Inject]
public IReactiveProperty<AppState> Store { get; set; } = default!;
[Inject]
public IDispatcher<AppState> Dispatcher { get; set; } = default!;
[Inject]
public AppEffects AppEffects { get; set; } = default!;
public int CurrentCount => Store.Value.Count;
private int Amount { get; set; } = 1;
protected override void OnInitialized()
{
// Observe Count value and if it was changed, then call StateHasChanged to refresh UI.
Store.Select(x => x.Count)
.DistinctUntilChanged()
.Subscribe(x =>
{
_ = InvokeAsync(() => StateHasChanged());
})
.AddTo(_disposables);
}
public void Dispose() => _disposables.Dispose();
private void IncrementCount()
{
// Dispatch IncrementAction to reducers
Dispatcher.Dispatch(new IncrementAction(Amount));
}
private void DecrementCount()
{
// Dispatch DecrementAction to reducers
Dispatcher.Dispatch(new DecrementAction(Amount));
}
private async Task IncrementCountAfterTwoSecondsAsync()
{
// Async operation is dispatched using DispatchAsync method and EffectDelegate<TStore>.
await Dispatcher.DispatchAsync(AppEffects.IncrementAsync(Amount));
}
}
It works as below:
| Product | Versions Compatible and additional computed target framework versions. |
|---|---|
| .NET | net6.0 net6.0 is compatible. net6.0-android net6.0-android was computed. net6.0-ios net6.0-ios was computed. net6.0-maccatalyst net6.0-maccatalyst was computed. net6.0-macos net6.0-macos was computed. net6.0-tvos net6.0-tvos was computed. net6.0-windows net6.0-windows was computed. net7.0 net7.0 was computed. net7.0-android net7.0-android was computed. net7.0-ios net7.0-ios was computed. net7.0-maccatalyst net7.0-maccatalyst was computed. net7.0-macos net7.0-macos was computed. net7.0-tvos net7.0-tvos was computed. net7.0-windows net7.0-windows was computed. net8.0 net8.0 was computed. net8.0-android net8.0-android was computed. net8.0-browser net8.0-browser was computed. net8.0-ios net8.0-ios was computed. net8.0-maccatalyst net8.0-maccatalyst was computed. net8.0-macos net8.0-macos was computed. net8.0-tvos net8.0-tvos was computed. net8.0-windows net8.0-windows was computed. net9.0 net9.0 was computed. net9.0-android net9.0-android was computed. net9.0-browser net9.0-browser was computed. net9.0-ios net9.0-ios was computed. net9.0-maccatalyst net9.0-maccatalyst was computed. net9.0-macos net9.0-macos was computed. net9.0-tvos net9.0-tvos was computed. net9.0-windows net9.0-windows was computed. net10.0 net10.0 was computed. net10.0-android net10.0-android was computed. net10.0-browser net10.0-browser was computed. net10.0-ios net10.0-ios was computed. net10.0-maccatalyst net10.0-maccatalyst was computed. net10.0-macos net10.0-macos was computed. net10.0-tvos net10.0-tvos was computed. net10.0-windows net10.0-windows was computed. |
This package is not used by any NuGet packages.
This package is not used by any popular GitHub repositories.