-
-
Notifications
You must be signed in to change notification settings - Fork 15.3k
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 our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Optimize dispatch plain object check #2599
Conversation
Looks okay at first glance, although the nuances of JS object detection are something I've never had to deal with. Do we have some tests around this scenario? |
I saw an email from a review comment come through, but I'm not seeing that comment as I'm looking at the diff. The email said:
Anyone more knowledgeable have thoughts on that? |
Another alternative to checking |
Constructor checks should be enough IMO. |
The Does anyone know the history of using plain object checks over simpler ones? |
@jdalton It's a good safety net for someone that makes a mistake when setting up something like redux-thunk or another middleware. Happens a fair amount and is more friendly to beginners. |
Could it be something that's available in dev builds and removed in production? How are similar debug helpers handled in react? |
We have some dev-only checks in combineReducers, but not much else. Unfortunately, it's easy to miss a lot of these issues without 100% test coverage, so having "friendlier" errors in production is pretty helpful to our users. Other than this strict check, they are mostly fairly simple and performant enough to warrant leaving it. AFAIK, React still uses the dev-expression babel plugin and |
Couple more questions:
Per 1, I know I've seen several addons that do stuff like serializing actions over |
Seems unlikely. Most folks use object literals.
This I have zero clue about. Not saying that it's not possible, just that I've never done it and don't know the implications. Can someone wire up a demo so we can test it out? BTW, my company does this in our Chrome extension. We have a background page with the Redux store that is replicated to the frontend content scripts. We do phone calls, so we need a single place to maintain our dialer's state. I'll ask the engineer who built that out. |
src/combineReducers.js
Outdated
@@ -26,7 +25,7 @@ function getUnexpectedStateShapeWarningMessage(inputState, reducers, action, une | |||
) | |||
} | |||
|
|||
if (!isPlainObject(inputState)) { | |||
if (!inputState || inputState.constructor !== Object) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this would work across iframes or multiple realms. Does it? Are we sure that this is a superset of Lodash's true
answer rather than a subset or intersection?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think this would work across iframes or multiple realms. Does it?
No it wouldn't. Or objects created with Object.create(null)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, I know about this problem. Hence the PR instead of just blasting this into master and calling it a day 😄
It's not a use case I'm familiar with, so I'd love some help understanding it and coming up with some examples or even tests for it.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sry for the noob question but why inputState.constructor !== Object
instead of typeof inputState === 'object'
?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@phsantiago Because object type can represent any object new Number
or new Array
for example. Instance of Object
is always plain.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@nasreddineskandrani Nope, but type checker will say what's wrong. For IDE fans there is small boilerplate like this
export const DO_ONE_THING = 'DO_ONE_THING';
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TrySound Correct me if I'm wrong, according to you:
It means it's not serializable -> may have side effects -> not predictable. And maybe redux devtools won't work too :)
If this won't break anything, developer should be able to bypass it, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@TrySound
please read enum string introduced in ts2.4 section here: https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-4.html
enum string => give you intellisense ability (save time) and type checking.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@aminpaks , @nasreddineskandrani , @TrySound : I do appreciate the enthusiasm and interest, but this discussion would probably be better off in a different location.
The Reactiflux chat channels at https://www.reactiflux.com always have plenty of people around who are happy to discuss Redux-related topics. There's somewhat less discussion of TypeScript or NgRX, but there might be some people who can discuss those.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@markerikson I hesitate to comment but since the code was in a sub thread. I gived my 2 cents. Thanks for the link I ll be happy to exchange about redux their.
My two cents:
|
<Retitling this so it doesn't come across as a dig against lodash> |
More research questions:
|
@gaearon , I've anecdotally seen plenty of cases where people are firing off way more than "one or two actions a second". I agree that subscribers are generally the bottleneck, but if we can reasonably say that there's some "plain object" edge cases that we aren't concerned with and improve dispatching perf overall, it's worth at least investigating. |
The problem is that we'll likely get false negatives for plain objects rather than false positives. In other words we'll now throw for things that are actually completely okay (and are plain objects). This is hugely disruptive to people who already rely on current behavior in their apps, sometimes without a good alternative.
OK. Let's say 60 dispatches per second is the maximum I would consider reasonable. Does this PR make a measurable difference in this case? |
@gaearon I have run a sample with 60 dispatches before and apter applying the patch:
|
@timdorr @gaearon I can suggest a lazy assertion.
Benefits: we do not punish the regular users, but in edge cases there may be performance overheads IMHO I think we should leave Lodash, because that's a heavy dependency. if (!action || (action.constructor !== Object && !action.__proto__ && typeof action !== 'object')) {
throw new Error('Actions must be plain objects. ' + 'Use custom middleware for async actions.');
} |
I like this (idea, not the code snippet which I think doesn't do that). Any downsides? |
Are there objects with |
I can imagine some frequently used dispatch: logging. I've used Redux for this, and I was glad by its performance. I think the performance improving is a cardinal purpose, because Redux is a very essential library in the most of React projects. If we can make it smaller and faster, that can only be good. |
@gaearon I tried my snippet with regular and Idea: we should use Lodash's test cases. |
What I'm trying to say is whatever the cost of |
react-redux also uses lodash's isPlainObject to test the results of mapXToProps. It only does it during dev builds, not production. It seems like the intent of this code is the same; to make sure the developer isn't doing anything wacky. By the time it gets to prod build, the issue should have been resolved. |
I think
But if you're using postMessage, the communication is serialized, so it shouldn't be an issue. Typically taking a variable from another frame is a bad idea in general. |
I'm going to make this held off for 4.0, so we can get more extensive testing via the prerelease cycle. It's now rebased against the Just to be clear, I'm on the same side of not seeing this being about a speed optimization, as dispatches should be relatively infrequent in most applications and not a bottleneck. Instead, it's a good chance to drop an external dependency that brings a fair amount of bytes with it. It's not huge, but every little bit helps. But the key blocker here is equivalency of the plain object check. I'd love a simple function for doing that. What would be most helpful here is a test harness I can use to ensure this is equivalent. Something around message passing via iframes and fun edge case stuff. I think we can do it 👍 |
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
* Simplify object check and eliminate lodash depdency, mirroring reduxjs/redux#2599 . * Add missing files. * Replace isPlainObject with unmodified version from Redux * Also replace the spec with the unmodified version
Closes #2598
This boosts perf by not exhaustively checking every action of it's object-ness. Dispatch is approximately 56x faster. Across a million runs of simple counter reducer:
I also applied the same thing in combineReducers. I believe the check is sufficient, but I'd love a second set of eyes on it.
We also no longer need to bundle any of lodash, so this saves a good number of bytes too! From the UMD build: