Git Product home page Git Product logo

redux.net's Introduction

Redux.NET

Redux.NET is an attempt to bring Redux concepts to .NET application development. Redux.NET is a PCL project, so it can be used with Xamarin (IOS, Android, Forms), Windows Universal applications, Windows 8.1 applications, WPF, etc...

Build status Nuget

Table of Contents

Motivation

After working on several MVVM applications (Silverlight, WPF, WinRT), I always feel uneasy when they start to get larger :

  • Two-way bindings lead to cascading updates, making it very difficult to predict what would change as the result of a single user interaction.
  • Asynchronous operations make even more laborious to keep the flow of the application in mind.

Dan Abramov, a brilliant javascript developer, faced the same kind of problems with complex web application. His solution? Redux!

The whole state of your app is stored in an object tree inside a single Store. The only way to change the state tree is to emit an Action, an object describing what happened. To specify how the actions transform the state tree, you write pure Reducers.

Installation

You can grab the latest Redux.NET Nuget package or from the NuGet package manager console :

Install-Package Redux.NET

Quick-start

Actions

Actions are payloads of information that send data from your application to your store. They only need to implement the markup interface Redux.IAction.

public class IncrementAction : IAction { }
    
public class DecrementAction : IAction { }
    
public class AddTodoAction : IAction
{
    public string Text { get; set; }
}

Reducers

A reducer is a pure function with ((TState)state, (IAction)action) => (TState)state signature. It describes how an action transforms the state into the next state.

The shape of the state is up to you: it can be a primitive, an array or an object. The only important part is that you should not mutate the state object, but return a new object if the state changes.

namespace Redux.Counter.Universal
{
    public static class CounterReducer
    {
        public static int Execute(int state, IAction action)
        {
            if(action is IncrementAction)
            {
                return state + 1;
            }

            if(action is DecrementAction)
            {
                return state - 1;
            }

            return state;
        }
    }
}

Store

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 updated via Dispatch(IAction action).
  • Registers listeners via Subscribe(IObserver observer). The Store<TState> class implements IObservable so ReactiveExtensions is a usefull tool to observe state changes.

It’s important to note that you’ll only have a single store in a Redux application. In the examples, I keep it as a static property on the application class.

The Store constructor take an initial state, of type TState, and a reducer.

using Redux;
using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;

namespace Redux.Counter.Universal
{
    public sealed partial class App : Application
    {
        public static IStore<int> CounterStore { get; private set; }

        public App()
        {
            InitializeComponent();
            
            CounterStore = new Store<int>(reducer:CounterReducer.Execute, initialState:0);
        }
    
        [...]
    }
}

The following code show how to subscribe to a store and to dispatch actions.

using Redux;
using System;
using Windows.UI.Xaml.Controls;
using System.Reactive.Linq;

namespace Redux.Counter.Universal
{
    public sealed partial class MainPage : Page
    {
        public MainPage()
        {
            this.InitializeComponent();

            App.CounterStore.Subscribe(counter => CounterRun.Text = counter.ToString());
        }

        private void IncrementButton_Click(object sender, Windows.UI.Xaml.RoutedEventArgs e)
        {
            App.CounterStore.Dispatch(new IncrementAction());
        }

        [...]
    }
}

Performance Optimization

Let's take a closer look to a slightly modified version of the todos app example :

    public sealed partial class MainSection : UserControl
    {
        public MainSection()
        {
            this.InitializeComponent();

            App.Store
                .Subscribe(state => TodosItemsControl.ItemsSource = GetFilteredTodos(state));
        }
        
        public static IEnumerable<Todo> GetFilteredTodos(ApplicationState state)
        {
            if (state.Filter == TodosFilter.Completed)
            {
                return state.Todos.Where(x => x.IsCompleted);
            }

            if (state.Filter == TodosFilter.InProgress)
            {
                return state.Todos.Where(x => !x.IsCompleted);
            }

            return state.Todos;
        }
    }

In this example, GetFilteredTodos is calculated every times the application state changed. If the state tree is large, or the calculation expensive, repeating the calculation on every update may cause performance problems. ReactiveExtensions can help us to avoid unnecessary recalculations.

DistinctUntilChanged to the rescue

We would like to execute GetFilteredTodos only when the value state.Filter or state.Todos changes, but not when changes occur in other (unrelated) parts of the state tree. Rx provide an extension method on IObservable DistinctUntilChanged that surfaces values only if they are different from the previous value based on a key selector (or IEqualityComparer). We just need to call DistinctUntilChanged with the appropriate keySelector just before subscribing to the store. Anonymous Types are a handy tool to create selectors since two instances of the same anonymous type are equal only if all their properties are equal. (See Remarks section : Anonymous Types)

To compute GetFilteredTodos only when necessary, we need to modify the previous code like this :

    App.Store
        .DistinctUntilChanged(state => new { state.Todos, state.Filter }) 
        .Subscribe(state => TodosItemsControl.ItemsSource = GetFilteredTodos(state));

Using DevTools

The development tools contain a time machine debugger inspired by Elm debugger and Redux DevTools.

You can get the dev tools package via nuget or via the Nuget package manager console :

Install-Package Redux.NET.DevTools

To use the time machine, just replace the Store with a TimeMachineStore and the application Frame with a DevFrame:

using Windows.ApplicationModel.Activation;
using Windows.UI.Xaml;
using Windows.UI.Xaml.Controls;
using Redux.DevTools.Universal;

namespace Redux.Counter.Universal
{
    public sealed partial class App : Application
    {
        public static IStore<int> CounterStore { get; private set; }

        public App()
        {
            InitializeComponent();
            
            CounterStore = new TimeMachineStore<int>(CounterReducer.Execute, 0);
        }

        protected override void OnLaunched(LaunchActivatedEventArgs e)
        {
            Frame rootFrame = Window.Current.Content as Frame;

            if (rootFrame == null)
            {
                rootFrame = new DevFrame
                {
                    TimeMachineStore = (IStore<TimeMachineState>)CounterStore
                };
                Window.Current.Content = rootFrame;
            }

            if (rootFrame.Content == null)
            {
                rootFrame.Navigate(typeof(MainPage), e.Arguments);
            }

            Window.Current.Activate();
        }
    }
}

Examples

  • Counter (sources)
    • Windows universal app : Redux.Counter.sln
  • Todo app (sources)
    • Windows universal app : Redux.TodoMvc.Universal.sln
    • Xamarin Andriod : Redux.TodoMvc.Android.sln
    • Xamarin Forms (IOS + Android) : Redux.TodoMvc.Forms.sln
  • Async http (sources)
    • Windows universal app : Redux.Async.sln

License

MIT

redux.net's People

Contributors

guillaumesalles avatar m-zuber avatar rid00z avatar touchesir avatar vinodsantharam avatar

Watchers

 avatar  avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.