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

Use React.createContext() by @markerikson#995

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

Closed
markerikson wants to merge13 commits intomasterfromv6-experiment-2

Conversation

@markerikson
Copy link
Contributor

@markeriksonmarkerikson commentedAug 12, 2018
edited
Loading

FOR DISCUSSION ONLY - DO NOT MERGE

This is a second attempt at the work done in#898 . Per#950 , the plan for React-Redux v6 is to use the new context API instead of the old deprecated context API.

This PR combines lessons learned from the work on a notional 5.1 PR in#980 , the first new context attempt in#898 , and a bunch more of me banging on things with a hammer until the tests shut up.

Note that this PR doesnot make use ofReact.forwardRef, and indeed I've just ripped out all of thewithRef stuff for now. I'm just trying to see if the data flow works correctly. It also removes any ability to pass the store as a prop to a connected component. In addition, I also haven't tried out any potential perf optimizations around context andobservedBits, either.

Summary of Changes

  • As withReact 16 experiment: rewrite React-Redux to use new context API #898 ,<Provider> is now the only store subscriber. I've attempted to follow the pattern shown by Brian Vaughn in hiscreate-subscription package.
  • Like 898, I'm putting the entire Redux store state into context. Unlike 898, I'm also putting the entire actual store into context as well, rather than justdispatch. Long-term, I'm probably going to leave it this way so that other libraries could intercept the store and wrap it up with their own behavior (as discussed inConsolidated React 16.3+ compatibility and roadmap discussion #950 ).
  • <Connect> has been split into two wrapper components. The outer one mostly just renders the<ReactReduxContext.Consumer> and the<ConnectInner> components. The real work is now done in<ConnectInner>, mostly ingetDerivedStateFromProps.

I've really been wanting to avoid needing to use a second wrapper component, but the use ofContext.Consumer pretty much mandates it due to the reliance on render props. I was originally planning on trying to use the newunstable_readContext() API (as described infacebook/react#13139 ) to stick with a single wrapper component, but Sebastian and Brian seem to think that's not a good idea.

This branch temporarily reverts all of our tests back to the older structure we had before the recent update for multiple React versions and use of Enzyme, due to Enzyme's inability to handle new context. (Yes, yes, Dan, I know you were making a point about that.) We're going to try converting the tests over to usereact-testing-library instead, but I just wanted to see what I could get working before then.

We just merged in#998 , which converts our tests to usereact-testing-library. I've re-created the code and test commits for this PR, and force-pushed them to this branch.

I've deleted a few tests that were obviously outdated (like making sure the store is in old context at the right key), and I'm also skipping some tests that either seem no longer relevant or aren't something I want to tackle right now:

'should unsubscribe before unmounting' 'should recalculate the state and rebind the actions on hot update' 'should use the store from the props instead of from the context if present' 'should throw an error if the store is not in the props or context' 'should throw when trying to access the wrapped instance if withRef is not specified' 'should return the instance of the wrapped component for use in calling child methods' 'should subscribe properly when a new store is provided via props'

Other than that, the testsshould be passing.

I've been trying to set up a perf benchmark harness over in#983 . I tried running v5.0.7 and this PR through that one stock ticker benchmark I've got set up.

Run with 1000 connected components, both versions stayed between 56-60 FPS. When I tried cranking it up to 2500 connected components, 5.0.7 dropped to between 20-27 FPS, while this PR was around 18-22. (For comparison, Jim's original benchmarks in#416 showed that v5 ran at 60 FPS with 330 components, while v4 choked down to single digits.)

In general, I would expect this PR to be a bit slower than v5 in some ways because of the overhead required in doing reconciliation for each new state, but faster in other because we're not immediately running all thesemapState calls.

I fully expect this PR is still flawed in a bunch of ways, but it's a good starting point for further discussion based on lessons learned so far.

Obligatorily tagging@timdorr ,@jimbolla ,@cellog ,@gaearon ,@acdlite ,@bvaughn,@sebmarkbage for thoughts.

alvis reacted with thumbs up emoji
@markerikson
Copy link
ContributorAuthor

For funzies, here's zipped-up copies of Chrome perf trace captures from the 2500-component benchmark run:

Perf benchmarks - 5.0.7 vs PR 995.zip

Anyone ought to be able to replicate this by:

  • Clonehttps://github.com/markerikson/benchmark-react-redux-perf
  • Build this PR branch
  • Name the prod UMD filereact-redux-6.0-test2.min.js
  • Copy it to the benchmark
  • Set the benchmark's connected components constant to 2500 and rebuild
  • Set the perf runner's versions to["5.0.7", "6.0-test2"] and run it

returnChildren.only(this.props.children)
return(
<ReactReduxContext.Providervalue={this.state}>
{Children.only(this.props.children)}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Children.only is no longer required, no?

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Hmm. Y'know, I'm not entirely sure why we even ever had the requirement of a single child to begin with. Perhaps this is avery old holdover from the earliest prototypes ofconnect.

I don't immediately see a reason why we'd still need to enforce only one child.

cellog, deecewan, and sebinsua reacted with thumbs up emoji
if(this[storeKey]!==this.props.store){
Provider.getDerivedStateFromProps=function(props,state){
if(state.store!==props.store){
warnAboutReceivingStore()
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

this is not required with the new context any longer, we can change the context value and children are updated

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

But, the selector initialization and binding of action creators happens when the connected children are instantiated - we don't try to re-bind those if the store is swapped out.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We can rebind in cDU, 1 line change

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Not something I'm particularly worried about right now. There may besomeone out there who has a need to swap out their store, but it's not something we've ever supported and I don't see a need to try to support it for the time being.

*/

renderInner(providerValue){
const{storeState, store}=providerValue;
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

here, I would rather do the calculation of derived props and memoize them, then the inner component need only be the WrappedComponent or a PureComponent that wraps it, something like:

constInner=connectOptions.pure ?classInnerextendsPureComponent{render(){return<WrappedComponent{...this.props}/>}} :WrappedComponent

then renderInner can be something like:

renderInner({ storeState, store}){constprops=this.propsconstderivedProps=this.memoizedProps(storeState,props)return<Inner{...props}{...derivedProps}/>}

Our memoizer can be initialized in the constructor withthis.memoizedProps = this.makeMemoizer()

makeMemoizer(){letlastPropsletlastStateletlastDerivedPropsletcalled=falsereturn(state,props)=>{if(called){constsameProps=connectOptions.pure&&shallowEqual(lastProps,props)constsameState=lastState===stateif(sameProps&&sameState){returnlastDerivedProps}}called=truelastProps=propslastState=stateconstnextProps=this.state.childPropsSelector(state,props)if(shallowEqual(lastDerivedProps,nextProps)){returnlastDerivedProps}lastDerivedProps=nextPropsreturnlastDerivedProps}}

In this way, we don't needgetDerivedStateFromProps at all

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

Let's try that approach as a second PR, for comparison purposes.

cellog reacted with thumbs up emoji
Connect.getDerivedStateFromProps=getDerivedStateFromProps

// TODO We're losing the ability to add a store as a prop. Not sure there's anything we can do about that.

Copy link
Contributor

@cellogcellogAug 12, 2018
edited
Loading

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

I still think we can manage this, but by allowing passing a separate context consumer to connect. This way, users can use a custom context provider and just hook into it with their specific components. In other words, we providecreateProviderContext and allow:

const{ SpecialProvider, SpecialConsumer}=createProviderContext()constConnectedNormal=connect(...)(Thing)constConnectedOther=connect(mSP,mDP,merge,{contextConsumer:SpecialConsumer})(Thing)functionSomething(){return(<Providerstore={store}><Providercontext={SpecialProvider}store={store2}><ConnectedNormal><ConnectedOther/></ConnectedNormal></Provider></Provider>)}

This way, we get the full advantage of React's context, but have flexibility.

Copy link
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others.Learn more.

We could potentially allow passing aContext.Consumer directly to a connected component as a prop, although we might have to do some fiddling around because this currently tries to pass all the wrapper props down directly to the inner component.

That said, I'm not worried about trying to implement store-as-a-prop or anything like that atm. My main concern is getting the data flow and update behavior working right - we can add everything else after that.

cellog reacted with thumbs up emoji
@markerikson
Copy link
ContributorAuthor

@cellog ,@timdorr : I had a chance to show this PR and#1000 to@bvaughn earlier today. He's not familiar with the guts of React-Redux, but he didn't see any obvious flaws or problems with either approach. He did think that implementing most of the logic with memoization inrender (the approach in#1000 ) might be a bit simpler and easier to maintain, but otherwise nothing glaringly wrong either way.

cellog and alvis reacted with thumbs up emoji

Added ability to swap storesRemoved single-child limitationAdded invariant warnings for storeKey and withRefAdded valid element check using react-isRefactored child selector creation for reusabilityAdded prop types for componentsAdded forwardRef and consumer as prop capabilityAdded a tiny memoized function for wrapper props handlingRemoved semicolons
@markerikson
Copy link
ContributorAuthor

I updated our benchmarks repo tonight, updated this#995 branch to makeforwardRef optional, and then rebuilt from#995 and#1000 and ran the benchmarks. Current results:

Results for benchmark stockticker:+-----------------------------------------------------------------¦ Version          ¦ Avg FPS ¦ Scripting ¦ Rendering ¦ Painting ¦ ¦ 5.0.7            ¦ 15.95   ¦ 16549.43  ¦ 10420.52  ¦ 2045.64  ¦ ¦ 6.0-1000-4855c84 ¦ 14.90   ¦ 18243.08  ¦ 8895.77   ¦ 1741.93  ¦ ¦ 6.0-995-9210282  ¦ 12.76   ¦ 20039.79  ¦ 7729.26   ¦ 1503.05  ¦ +-----------------------------------------------------------------Results for benchmark tree-view:+----------------------------------------------------------------¦ Version          ¦ Avg FPS ¦ Scripting ¦ Rendering ¦ Painting ¦¦ 5.0.7            ¦ 44.00   ¦ 9627.22   ¦ 9947.20   ¦ 662.78   ¦¦ 6.0-1000-4855c84 ¦ 42.39   ¦ 13272.30  ¦ 7828.64   ¦ 526.37   ¦¦ 6.0-995-9210282  ¦ 41.71   ¦ 14573.84  ¦ 7497.27   ¦ 500.23   ¦+----------------------------------------------------------------Results for benchmark twitter-lite:+----------------------------------------------------------------¦ Version          ¦ Avg FPS ¦ Scripting ¦ Rendering ¦ Painting ¦¦ 5.0.7            ¦ 57.86   ¦ 21355.14  ¦ 6291.97   ¦ 720.06   ¦¦ 6.0-1000-4855c84 ¦ 56.35   ¦ 22500.03  ¦ 5506.43   ¦ 679.90   ¦¦ 6.0-995-9210282  ¦ 53.26   ¦ 23057.37  ¦ 5031.57   ¦ 654.91   ¦+----------------------------------------------------------------

So, what I'm seeing is that both#995 and#1000 are within shouting distance of 5.0.7 in stress-test scenarios. 1000 is a bit slower than 5.0.7, and 995 is a bit further back. The bad news is that both seem to be spending noticeably more time "scripting". To be honest, I'm not sure we can do anything about that. 5.0.7 can bail out early because it runs its memoized selectors directly in the subscription callbacks and quit before it actually asks React to update. Both 6.0 branches are reliant on React updating the component tree and propagating the new state through context.

I'd like us to do some investigation into where these two implementations are spending their time, and see if we can do any further perf optimizationsother than theobservedBits stuff. After that, hopefully we can come to some kind of conclusion on which approach we want to go with for v6.

@cellog
Copy link
Contributor

Why did you disable the context observedBits stuff in the benchmark? That was important for performance, perhaps the single biggest enhancement

@markerikson
Copy link
ContributorAuthor

Right, that's exactly why I disabled them. I wanted to see how the two branches currently perform without anyobservedBits behavior. Also, for now I want us to focus on any additional optimizations we can do withoutobservedBits, and then come back to it later.

If you want to create a branch in the perf repo that has the context usage in the apps so we can keep them around, go ahead.

@timdorrtimdorr changed the titleReact 16 experiment #2: rewrite React-Redux to use new contextUse React.createContext() by @markeriksonSep 20, 2018
@timdorr
Copy link
Member

The latest version of this PR is now published to npm under thenext-995 tag:https://www.npmjs.com/package/react-redux?activeTab=versions

npm install react-redux@next-995

@timdorr
Copy link
Member

Oh, and renamed this and#1000 so they pair up well in the PR list and browser's tab bar.

@timdorrtimdorr mentioned this pull requestOct 10, 2018
@timdorrtimdorr mentioned this pull requestOct 24, 2018
@timdorr
Copy link
Member

Given that#1000 is slightly faster and appears to have more finalized code (no TODOs), I'm going to close this out so we've only got one PR to think through.@cellog has granted us edit permissions on his PR branch, so we can focus any development efforts there.@markerikson, if you wanted to bring over any code to that PR, feel free.

@timdorrtimdorr deleted the v6-experiment-2 branchOctober 28, 2018 02:29
Sign up for freeto join this conversation on GitHub. Already have an account?Sign in to comment

Reviewers

1 more reviewer

@cellogcellogcellog left review comments

Reviewers whose approvals may not affect merge requirements

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

@markerikson@cellog@timdorr

[8]ページ先頭

©2009-2025 Movatter.jp