Closed ltjax closed 4 years ago
Hi Marius,
Thanks for the explanation. I find your Lens reducer feature interesting.
From what I understand, I see 2 distinct points :
Builder
pattern to simplify creation of reducersFor the first point, I can think to use it with the current sub-reducers feature. I don't think it is related to your Lenses. So why not enjoy this feature, I will look at it.
And for the second point, I want you to know that ReduxSimple
is mainly inspired by @ngrx. On this part, @ngrx uses feature key (the string key of a property inside the Root state) to handle nested reducers. But even in a js/ts application using @ngrx, I am not really a fan, I would rather prefer a lambda function. And this case, I think that 1. reusing a selector is smart (no code duplication) and 2. the selectors act as lenses since we use them to access the data and we use .NET reflection to update the data.
So, I will definitely look into the first point to improve the writing of reducers. And about the lenses, if you agree or if you want to share a more concrete example, please do. It's possible that I may have missed something.
And again, thank you for your sharing.
I'm not entirely sure what your status is here. But I see that you recently (11 days ago) changed your implementation from writing to the first property with the right type to the first property that has the same value. This does, however, also break easiely with anything that has a proper == - you just need to try to reduce to the second of two ints with the same value, and it'll break.
The current sub reducer in ReduxSimple breaks in a lot of strange ways because it does not have a sensible "write-back" path. Besides the value-equality problem, it'll break when you select something that is deeper in your state, for example we use a selector like state => state.SomeList[i]
. Correct me, if I'm wrong, but I believe that will just silently fail (i.e. do nothing) with the ReduxSimple CreateSubReducers.
The lens thing is conceptually a super-set of your current solution, with an explicit write-back path (I call this "join").
And you can very easiely layer a property-key based selector as a facade on top of it. If you want to use reflection, you probably need to use an Expression<>
or you will only ever get enough info for the "slice" part, not the write-path/join.
I see your point and so here are the different use cases:
struct
, int
, string
) => it won't work for the simple reason that will have a compile error (TState
must be class
)So, in order to fix this, I see that we can have an ImplicitLens
(current implementation) and an ExplicitLens
, like you suggested.
And if I try to prototype an API that could make this work:
internal class ImplicitStateLens : IStateLens<TState, TFeatureState> {}
internal class ExplicitStateLens : IStateLens<TState, TFeatureState> {}
static IStateLens<TState, TFeatureState> CreateSubReducers(Func<TState, TFeatureState> featureSelector) {} // implicit lens
static IStateLens<TState, TFeatureState> CreateSubReducers(ISelectorWithoutProps<TState, TFeatureState?> featureSelector) {} // implicit lens
static IStateLens<TState, TFeatureState> CreateSubReducers(
Func<TState, TFeatureState> featureSelector,
Func<TState, TFeatureState, TState> featureReducer
) {} // explicit lens
static IStateLens<TState, TFeatureState> CreateSubReducers(
ISelectorWithoutProps<TState, TFeatureState?> featureSelector,
Func<TState, TFeatureState, TState> featureReducer
) {} // explicit lens
In a sense that the implicit Lens will work exactly like now but it will write a console warning, things that I should have done already...
And of course, we can still use the Builder pattern to make it even better.
public static IEnumerable<On<RootState>> GetReducers()
{
return CreateSubReducers(SelectCounterState, ReduceCounterState)
.On<ActionA>(ReduceOnActionA)
.On<ActionB>(ReduceOnActionB)
.ToList();
}
public static IEnumerable<On<RootState>> CreateReducers()
{
return CombineReducers(
Counter.Reducers.GetReducers(),
TicTacToe.Reducers.GetReducers(),
TodoList.Reducers.GetReducers(),
Pokedex.Reducers.GetReducers()
);
}
I'd love to see that. What do you think?
Yea, it looks nice!
The indexed thing will only work if you can also read something from the action to affect the index, otherwise you can just mirror it onto another property on the state. But then it is very useful. Either way, the "implicit" sub reducer should still be able to access more deeply nested properties like state => state.A.B
I think you can still use a "real" property key / reflection to make the implicit implementation better. To do this, you must first decode an expression like state => state.A.B
into a "property key" like List<string>{"A", "B"}
. Then you can access the properties for reading and writing.
This can be done with .NET reflection, but it is a bit slow. That shouldn't be a problem, since it only needs to be done once when setting up the reducers. I can give you the code to decode the expressions, if you want.
I opened a draft PR if you want to read the code. I think it really stay dumb and simple.
Clearly, the explicit state lens is way better in many way. I just wanted something simple as reuse the selector and just go for it
. Anyway, I will make the explicit version one the default one in the doc.
@ltjax Like I said, I merged the PR as it is. If you want, you can open another issue/PR if you want this to go further. I will also add you in the contributions list.
Hello David. As I teased on twitter, we have been using a different kind of sub/feature reducer. I noticed that the sub reducers that are already in ReduxSimple really dispatch to the type of the property, which led to our homegrown reducer for "parts of the state", with a few differences.
We're using the name "LensedReducer" internally, but I'm pretty sure we're not using the concept of the functional lense correctly here. Here's how you'd define such a thing:
Adn we use that like this to generate a list of Ons:
Here are the classes:
What do you think?