Trait composable::Reducer
source · pub trait Reducer {
type Action;
type Output;
// Required method
fn reduce(&mut self, action: Self::Action, send: impl Effects<Self::Action>);
}
Expand description
Reducer
s are responsible for updating a Store
’s state in response to its Action
s.
Required Associated Types§
sourcetype Output
type Output
Both unit tests and command line applications often need to return their Store
’s final
state. Therefore Store
’s into_inner
method shuts down the Store
and converts its
Reducer
into its Output
type.
Using a separate Output
type, rather than returning the Reducer
itself, allows the
Store
s to support Reducer
types that are not Send
.
Reducer
s that do not need to supportinto_inner
should use declaretype Output = Self;
as it is a simple, recognizable default.- A
Reducer
that isSend
can also default totype Output = Self;
. - Otherwise, the
Reducer
will need to declare anOutput
type that isSend
and that can be cratedFrom
theReducer
’s state.
struct State {
n: Rc<Cell<usize>>, // Rc<Cell<…>> is not Send
};
enum Action { /* … */ }
impl Reducer for State {
type Action = Action;
type Output = usize; // but the usize itself _is_
fn reduce(&mut self, action: Self::Action, send: impl Effects<Self::Action>) { /**/ }
}
impl From<State> for usize {
fn from(value: State) -> Self {
Cell::into_inner(Rc::into_inner(value.n).unwrap_or_default())
}
}
In short, you can use type Output = Self;
until the compiler says that you can’t.
Required Methods§
sourcefn reduce(&mut self, action: Self::Action, send: impl Effects<Self::Action>)
fn reduce(&mut self, action: Self::Action, send: impl Effects<Self::Action>)
Updates the Reducer
’s state in response to the action received.
Additional Action
s that need to be performed as a side-effect of an Action
should be
invoked on effects
.
The logic of the feature is performed by mutating its current State
with Action
s.
#[derive(Clone, Debug, Default, PartialEq)]
struct State {
n: usize,
}
#[derive(Debug, PartialEq)]
enum Action {
Increment,
Decrement,
}
This is most easily done by implementing the Reducer
trait directly on it’s State
.
use Action::*;
impl Reducer for State {
type Action = Action;
type Output = usize;
fn reduce(&mut self, action: Action, _send: impl Effects<Action>) {
match action {
Increment => {
self.n += 1;
}
Decrement => {
self.n -= 1;
}
}
}
}
The reduce
method’s first responsibility is to mutate the feature’s current state given an action
. Its second responsibility is to trigger effects that feed
their actions back into the system. Currently reduce
does not need to run any effects so _effects
goes unused.
If the action does need side effects, then more would need to be done. For example, if reduce
always maintained an even number for the State
, then
each Increment
and Decrement
would need an effect to follow:1
use Action::*;
impl Reducer for State {
type Action = Action;
type Output = usize;
// This reducer ensures the value is always an even number
fn reduce(&mut self, action: Action, send: impl Effects<Action>) {
match action {
Increment => {
self.n += 1;
if self.n % 2 == 1 {
send.action(Increment); // ⬅︎
}
}
Decrement => {
self.n -= 1;
if self.n % 2 == 1 {
send.action(Decrement); // ⬅︎
}
}
}
}
}
- See
TestStore
for a more complete test of this example. - See
Effects
for all of the effects that can be used within aReducer
.
Granted, real code could just adjust the values by two. It is a contrived example to show how to use
effects
, after all. ↩