Port of MobX for the C# language.
Supercharge the state-management in your Blazor apps with Transparent Functional Reactive Programming (TFRP)
MobX is a state-management library that makes it simple to connect the reactive data of your application with the UI. This wiring is completely automatic and feels very natural. As the application-developer, you focus purely on what reactive-data needs to be consumed in the UI (and elsewhere) without worrying about keeping the two in sync.
It's not really magic but it does have some smarts around what is being consumed (observables) and where (reactions), and automatically tracks it for you. When the observables change, all reactions are re-run. What's interesting is that these reactions can be anything from a simple console log, a network call to re-rendering the UI.
MobX has been a very effective library for the JavaScript apps and this port to the C# language aims to bring the same levels of productivity.
At the heart of MobX are three important concepts: Observables, Actions and Reactions.
Observables represent the reactive-state of your application. They can be simple scalars to complex object trees. By defining the state of the application as a tree of observables, you can expose a reactive-state-tree that the UI (or other observers in the app) consume.
A simple reactive-counter is represented by the following observable:
using Skclusive.Mobx.Observable;
var counter = ObservableValue<int>.From(0);
More complex observables, such as classes, can be created as well.
using Skclusive.Mobx.Observable;
public class Counter
{
private readonly IObservableValue<int> _count = ObservableValue<int>.From(0);
public int Count
{
get => _count.Value;
set => _count.Value = value;
}
public void Increment()
{
_count.Value++;
}
}
What can be derived, should be derived. Automatically.
The state of your application consists of core-state and derived-state. The core-state is state inherent to the domain you are dealing with. For example, if you have a Contact
entity, the FirstName
and LastName
form the core-state of Contact
. However, FullName
is derived-state, obtained by combining FirstName
and LastName
.
Such derived state, that depends on core-state or other derived-state is called a Computed Observable. It is automatically kept in sync when its underlying observables change.
State in MobX = Core-State + Derived-State
using Skclusive.Mobx.Observable;
public class Contact
{
private readonly IObservableValue<string> _firstName;
private readonly IObservableValue<string> _lastName;
private readonly IComputedValue<string> _fullName;
public Contact()
{
_firstName = ObservableValue<string>.From();
_lastName = ObservableValue<string>.From();
_fullName = ComputedValue<string>.From(() => $"{FirstName}, {LastName}");
}
public string FirstName
{
get => _firstName.Value;
set => _firstName.Value = value;
}
public string LastName
{
get => _lastName.Value;
set => _lastName.Value = value;
}
public string FullName => _fullName.Value;
}
In the example above FullName
is automatically kept in sync if either FirstName
and/or LastName
changes.
Actions are how you mutate the observables. Rather than mutating them directly, actions
add a semantic meaning to the mutations. For example, instead of just doing Value++
,
firing an Increment()
action carries more meaning. Besides, actions also batch up
all the notifications and ensure the changes are notified only after they complete.
Thus the observers are notified only upon the atomic completion of the action.
Note that actions can also be nested, in which case the notifications go out when the top-most action has completed.
var counter = ObservableValue<int>.From(0);
var increment = Actions.CreateAction<int, int>("Increment", (amount) =>
{
counter.Value += amount * 2;
counter.Value -= amount; // oops
return counter.Value;
});
increment(2);
Reactions complete the MobX triad of observables, actions and reactions. They are
the observers of the reactive-system and get notified whenever an observable they
track is changed. Reactions come in few flavors as listed below. All of them
return a IReactionDisposable
, a disposable that can be called to dispose the reaction.
One striking feature of reactions is that they automatically track all the observables without any explicit wiring. The act of reading an observable within a reaction is enough to track it!
The code you write with MobX appears to be literally ceremony-free!
IReactionDisposable Reactions.Autorun(Action<IReactionPublic> action)
Runs the reaction immediately and also on any change in the observables used inside
action
.
using Skclusive.Mobx.Observable;
var greeting = ObservableValue<string>.From("Hello World");
var disposable = Reactions.Autorun((_) =>
{
System.Console.WriteLine(greeting.Value);
});
greeting.Value = "Hello Mobx";
// done with Autorun
disposable.Dispose();
// Prints:
// Hello World
// Hello MobX
IReactionDisposable Reactions.Reaction<T>(Func<IReactionPublic, T> expression, Action<T, IReactionPublic> effect)
Monitors the observables used inside the predicate()
function and runs the effect()
when
the predicate returns a different value. Only the observables inside predicate()
are tracked.
using Skclusive.Mobx.Observable;
var greeting = ObservableValue<string>.From("Hello World");
var disposable = Reactions.Reaction<string>((reaction) => greeting.Value, (value, reaction) =>
{
System.Console.WriteLine(greeting.Value);
});
greeting.Value = "Hello Mobx"; // Cause a change
// done with reaction()
disposable.Dispose();
// Prints:
// Hello MobX
Add a reference to the library from
This is an attempt to port mobx to dotnet-standard C# libaray.
Skclusive.Mobx.Observable is licensed under MIT license