Movatterモバイル変換


[0]ホーム

URL:


Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Key selectors composition#73

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.

Already on GitHub?Sign in to your account

Conversation

@sgrishchenko
Copy link
Contributor

@sgrishchenkosgrishchenko commentedApr 7, 2019
edited
Loading

What kind of change does this PR introduce?(Bug fix, feature, docs update, ...)

feature

What is the current behaviour?(You can also link to an open issue here)

Now if you try use cached selectors in dependecies, you need copy-paste all logic from their key selectors.

What is the new behaviour?

In this PR I propose solution for automatic key selectors composition of selector dependecies (will be composed only key selectors which returns strings).

Does this PR introduce a breaking change?(What changes might users need to make in their application due to this PR?)

This PR does not introduce a breaking change but changes some API:

  • keySelector become optional (default keySelector is() => '')
  • added newkeySeparator option (default value is: like inhere)
  • added newcomposeKeySelectors option for disable this behavior (default value istrue )

Other information:

When I use cached selector as dependency in simple selector, cached selector invalidates simple selector cache. To fix this I need to make simple selector cached (it is OK) and copy-paste key selector from dependecy (it is not OK). For example:

constdependency1=createCachedSelector(state=>state,(state,props)=>props.id,(state,id)=>state[id])((state,props)=>props.id);constdependency2=createCachedSelector(state=>state,(state,props)=>props.otherId,(state,id)=>state[id])((state,props)=>props.otherId);constcachedSelector=createCachedSelector(dependency1,dependency2,(first,second)=>({    first,    second}))((state,props)=>`${props.id}:${props.otherId}`);

All this leads to a terrible DX:

  • So easy when adding a dependency, forget to update key selector
  • If key selector from dependency contains some logic I need duplicate it
  • If I refactor dependency key selector, I need update all dependent selectors

If compose key selectors automatically you can rewrite previous example like this:

constcachedSelector=createCachedSelector(dependency1,dependency2,(first,second)=>({    first,    second}))();

As result key selector for cachedSelector will be

(state,props)=>`:${props.id}:${props.otherId}`

If you introduce additional props, you can write something like this:

constcachedSelector=createCachedSelector(dependency1,dependency2,(state,props)=>props.someProp(first,second,someProp)=>({    first,    second,    someProp}))((state,props)=>props.someProp);

And result key selector again will be composed to this:

(state,props)=>`${props.someProp}:${props.id}:${props.otherId}`

Please check if the PR fulfills these requirements:

  • Tests for the changes have been added
  • Docs have been added / updated

dkaledin, veksa, sv1809, and eaglus reacted with thumbs up emojidkaledin and eaglus reacted with laugh emojiveksa and eaglus reacted with hooray emojidkaledin, veksa, sv1809, and eaglus reacted with heart emojidkaledin and eaglus reacted with rocket emoji
@coveralls
Copy link

coveralls commentedApr 7, 2019
edited
Loading

Coverage Status

Coverage remained the same at 100.0% when pullingdd5e5d8 on sgrishchenko:key-selectors-composition into263c998 on toomuchdesign:master.

@toomuchdesign
Copy link
Owner

Hi@sgrishchenko, I see the point of supporting selectors' composability.

I'd like to consider the possibility of providing this feature as an external utility function. Something to directly provide askeySelector or maybe a sort of selectors composer likerecompose does.

@sgrishchenko
Copy link
ContributorAuthor

sgrishchenko commentedApr 8, 2019
edited by toomuchdesign
Loading

If I try provide external utility function, I receive something like this:

constkeySelector=combineKeySelectors(dependency1,dependency2,(state,props)=>props.someProp)constcachedSelector=createCachedSelector(dependency1,dependency2,(state,props)=>props.someProp,(first,second,someProp)=>({    first,    second,    someProp}))(keySelector);

As you can see, I need again copy-paste dependencies and again so easy when adding a dependency, forget to update key selector. What if I create separate functioncreateKeyComposedSelector(...) and encapsulate all additional logic in it?

@toomuchdesign
Copy link
Owner

It seems that the arguments of the hypotheticalcombineKeySelectors function are the same selectors provided asinputSelectors, right?

Not that I like this syntax, but as a proof of concept, might it be expressed like this?

constinputSelectors=[dependency1,dependency2,(state,props)=>props.someProp,];constcachedSelector=createCachedSelector(inputSelectors,(first,second,someProp)=>({    first,    second,    someProp}))(combineKeySelectors(...inputSelectors));

@sgrishchenko
Copy link
ContributorAuthor

sgrishchenko commentedApr 9, 2019
edited
Loading

At first, I can't pass all dependencies incombineKeySelectors function. There is three kinds of dependencies:

  • Re-reselect selectors (or other selectors, that haskeySelector property)
  • Simple state selectors (likestate => state.foo)
  • Property selectors, which probably should be used in key selector (like(state, props) => props.someProp)

For this reason, I cannot distinguish state selectors from property selectors and I need do something like this:

constkeySelectors=[dependency1,dependency2,(state,props)=>props.someProp,];constcachedSelector=createCachedSelector([state=>state.foo,state=>state.bar,    ...keySelectors,],(foo,bar,first,second,someProp)=>({    foo,    bar,    first,    second,    someProp}))(combineKeySelectors(...keySelectors));

Secondly, imagine that you future user ofre-reselect library. You have two helpers:createCachedSelector andcombineKeySelectors. As a good developer, the first thing you do is create a helper that combines these two helpers, because you don't want to copy-paste it every time. So why can't we do this out-of-box for library users?

Additionally, I understand your concern about granularity of this approach. I suggest to expose two new helper:

  • createKeyCombinedSelector (high level for common usage)
  • combineKeySelectors (low level for custom helpers)

What do you think about it?

@toomuchdesign
Copy link
Owner

For this reason, I cannot distinguish state selectors from property selectors and I need do something like this:

I've personally never dealt with such a scenario, therefore take what I say with a grain of salt: what ifcombineKeySelectors called each selector'skeySelector (when existing) or the selector itself (when nokeySelector is exposed) and chain onlystring | number results? It might have performance implications, of course.

It might get cumbersome (wrappers wrapping wrappers... mhh..), but I'd definitely go for thehelper approach.createKeyCombinedSelector exposes slightly different API's (eg. nokeySelector) and I considerpolymorphic API's a no go in this context.

I'd also suggest to find a way ofintroducing these non-core featuregradually: maybe aseparatere-reselect-util repo or exposing someexperimental non stable API's. What would you go for@sgrishchenko?

Same goes for source code and tests: I'd try to keepcore logic and the rest well separated. Rollup will think about shipping all together.

If there are still blurry areas about what these utility are supposed to do, we might consider writing down thedocumentation first.

Thanks for caring@sgrishchenko!
Greetings!

@toomuchdesign
Copy link
Owner

I just sawhttps://github.com/sgrishchenko/reselect-utils! Amazing job, there.

I'd keep this issue open as a remainder to update the docs to extensively mentionreselect-utils.

@toomuchdesign
Copy link
Owner

toomuchdesign commentedJun 8, 2019
edited
Loading

I'm investigating the feasibility of such an API for the use case@sgrishchenko mentioned:

importcreateCachedSelector,{combineKeySelectors}from're-reselect';constcachedSelector=createCachedSelector(dependency1,dependency2,(state,props)=>props.someProp(first,second,someProp)=>({    first,    second,    someProp}))(combineKeySelectors(options));

If we somehow extended the data provided to thekeySelector with theinputSelectors array,combineKeySelectors should have everything it needs to automatically create the combinedkeySelector.

@toomuchdesigntoomuchdesign mentioned this pull requestJun 9, 2019
2 tasks
@toomuchdesign
Copy link
Owner

toomuchdesign commentedJun 18, 2019
edited
Loading

Closing in favour of#78. Thanks@sgrishchenko for the inspiring contribution!

@toomuchdesign
Copy link
Owner

Hi@dkaledin@veksa@sv1809@eaglus,
with v3.3.0 introducingkeySelectorCreator option, dynamic keySelector composition is now a thing.

With#78 I also tried to provide akeySelectorCreator function to ship withre-reselect, but I realised I don't have enough hands-on experience about dynamic selector composition to come up with a valuable solution.

So I'm inviting you to provide some real-world examples and possible API's to reason about the possibility of shipping such akeySelectorCreator function in a way which is generic enough to live in this library.

Thanks to you all. Cheers!

sgrishchenko reacted with thumbs up emojisgrishchenko reacted with hooray emoji

@mbellman
Copy link

mbellman commentedJul 16, 2019
edited by toomuchdesign
Loading

@toomuchdesign@sgrishchenko Interesting following the back-and-forth here. I actually stumbled upon this thread through having misinterpreted the library similarly tothe users in this other thread, and coming to realize that cache key selection didn't follow the mechanism I originally believed.

I think some of the frustrations expressed here would be mitigated through a design or option which just provided dependency function results to the cache key selector automatically. This would eliminate redundancies between the 'actual' selector and the cache key selector. Although, maybe this is precisely what@sgrishchenko was alluding to with hiscreateKeyCombinedSelector idea. (I'm only just seeing/processing this discussion for the first time, so forgive me if I'm repeating notions already entertained.)

It is unfortunate that reselect does not expose itsinternal mechanism for building dependency results as an abstraction, meaning this library would have to replicate that behavior. However, allowing a key selector option to facilitate a predefined, "default"keySelectorCreator which behaved like this:

functioncreateDependencyResultsKeySelector(keySelector,dependencies){returnfunction(){constdependencyResults=dependencies.map(dependency=>dependency.apply(null,arguments));returnkeySelector.apply(null,dependencyResults);};};

would allow usage like this:

constselectABC=createCachedSelector(selectA,selectB,selectC,(a,b,c)=>compute(a,b,c))((a,b,c)=>`${a}:${b}:${c}`,{useDependencyResults:true});...// BoomselectABC(state);

There would of course be (potentially) redundant work betweenre-reselect andreselect if a cached result was not found, andreselect proceeded on its merry way determining the dependency function results again. Ideally, the dependency functions would have themselves been created with appropriate memoization or caching, so the results wouldn't actually be recomputed with proper usage, but the process of running through the dependencies and building the list again would be.

I do want to emphasize the utility of allowing this library to use dependency results, rather than just initial selector arguments, to derive cache keys. It can be unwieldy to have to independently determine and pass in those values in cases where the selectors aren't normally written to take them, just to ensure that I can take advantage ofre-reselect's superior caching ability. It also limits the ability of cached selectors to be used withcreateStructuredSelector in this fashion:

constmapStateToProps=createStructuredSelector({stuff:selectStuff,otherStuff:selectOtherStuff});

...unless I want to write my key selectors to re-select (hah!) the "dependency results" manually, fromstate, to derive the right cache keys. Which I can. It's just a little leaner when done automatically. : - )

I can't see getting around a certain amount of awkwardness either way. For example, this is a little awkward:

/** * Using the 'extra argument' form: selectPersonByName(state, 'John') */constselectPersonByName=createCachedSelector(selectPeople,// This 'dependency' just exists to forward the// selector's second argument to the result function(state,name)=>name,(people,name)=>people[name])((_,name)=>name);

and this is a little awkward:

/** * Using the 'single state argument' form: selectCurrentStudentTestAverage(state) */constselectCurrentStudentTestAverage=createCachedSelector(selectCurrentStudent,// This 'dependency' just exists to forward something// to the key selector (also, let's pretend currentStudent// does not already have the studentId property)selectCurrentStudentId,currentStudent=>{const{ testResults}=currentStudent;returntestResults.reduce((acc,result)=>acc+result,0)/testResults.length;})((_,studentId)=>studentId,{useDependencyResults:true});

Anyway, that's my 2 cents!

toomuchdesign and jchook reacted with heart emoji

@toomuchdesign
Copy link
Owner

toomuchdesign commentedAug 28, 2019
edited
Loading

Hi@mbellman,
I read your comment just now. Sorry, it was a quite busy time and I overlooked your message. Thanks for writing, though!

I 100% agree that the confusion about what argument are actually received by thekeySelector is an issue hitting al lot of users. Including me: I realised that I made the same mistake in the last docs update. I just opened a PR.

As you mentioned, there are a few reasons whykeySelector are provided with the initial selector arguments:

  • Duplicated logic betweenreselect andre-reselect
  • inputSelectors would be evaluated twice byreselect andre-reselect
  • inputSelectors results are not known at declaration time (they're evaluated on runtime): using them askeySelectors arguments would have implications that would need further clarifications

I will openan issue to reason about the opportunity of adding the possibility of providingkeySelectors withinputSelectors results instead of their same arguments.

I'm a bit reluctant to proceed lighthearted for 2 reasons:

  • providing both option could make the library still harder to grasp
  • TS typings complexity would increase exponentially

But I'm totally curious to better understand whether such a feature could provide real value.
Cheers! ✌️

Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

No reviews

Assignees

No one assigned

Labels

None yet

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

4 participants

@sgrishchenko@coveralls@toomuchdesign@mbellman

[8]ページ先頭

©2009-2025 Movatter.jp