-
Notifications
You must be signed in to change notification settings - Fork 558
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Accept calculateChangedBits as a prop on Context.Providers #60
Conversation
Thanks for writing this up. It's a well written RFC. I'd like to add some context about this API though. The calculateChangedBits API is a bit sketchy, likely to change. The context API in general is optimizing for things that rarely change and when it changes, isn't all that high priority. If you care about fine grained updates enough to care about calculate changed bits, maybe you should just use subscriptions instead? Just because there's a new API doesn't mean it's better. Being in the niche of having to use calculateChangedBits is a very small niche in the first place. Being in the niche of needing specific features out of calculateChangedBits is even less likely to being the best solution to the problem. If everyone needs such features to use it, then maybe we should just rip it out completely. It's already adding an uncomfortable amount of code and execution in hot paths. So that's the lens I'm viewing this RFC through. A few points:
|
In the case of React-Redux, we already use per-component-instance subscriptions up through the current version. The Redux store is currently passed down through legacy context, and every connected component instance is a separate subscriber to the store. We're currently attempting to transition to using Since Our current rough performance benchmarks indicate that the Answering your comments specifically:
In this case, I wouldn't say it's actually adding a "specific feature" around
Assuming I'm reading that correctly, 13293 would allow an end user to globally update the current value for a given I don't think this RFC changes anything related to that PR. The change I'm proposing only matters if you are actually rendering a
Implementation-wise, I think the example reimplementation code I wrote should work if the user either switches from giving a function to not giving one, or vice-versa. Your example of using separate "implementation context" and "value context" instances sounds like what I described in the "Alternatives" section of the RFC. To me having to use two separate
That sounds like exactly how I would want to use it :) In the RFC, I wrote this hand-waved function: function calculateChangedBitsForPlainObject(currentValue, nextValue) {
// magic logic to diff objects and generate a bitmask
} Here's how I imagine an un-hand-waved implementation would work. Let's say we've got a Redux store whose top-level state looks like A standard shallow equality check loops over the keys for two objects , and for each key, checks to see if the corresponding values in the two objects have changed. If any have changed, it returns false ("these objects are not equal"). I would want to implement a similar function that returns an array of all keys whose values have changed in the newer object (such as From there, I would run each key name through a hash function of some sort, which hashes the key string into a single bit. I briefly experimented with using a "minimal perfect hashing" library a while back as an early proof of concept. So, every time the Redux store updated, the React-Redux Connected components would by default still subscribe to all updates, but we would provide a way for the end user to indicate that a connected component only cares about updates to certain state slices (hypothetical example: |
I agree with this. I have thought about this(the hash solution, like immutable) before the new context api, to be honest I think this is irrelevant with the context api, even in old context api we could do this. But this is not about react-redux perf, it's just about reducer logic. Just some rough thought, we could provide a new api to allow developer update one deeper object/array, such as allow return a string(or return a object array, or provide a individual api) when use a reducer like
But I think maybe this is not a good idea, if we do this, we will put the complexity to the developer, this implementation( Finally, IMO, I don't think we could do something like this rfc to perf react-redux. Because redux is unaware of the relationship between component state dependencies and redux store. So, even we add a wrap like react-redux, we also can't get it, no matter how we do in react-redux. Once enter a normal world(like redux) we will lost any specific info no matter how where you from(like react-redux or something else). |
@NE-SmallTown : I'm not quite sure what point you're trying to make, and it doesn't exactly seem relevant to this RFC. The primary use case I'm describing in the RFC is indeed for use with React-Redux, largely because A) I do want to use it in React-Redux, and B) that's what I know the most about. I already provided examples of how React-Redux could benefit from this. But, I can see it being useful for other state handling libraries as well. The The rest of the comments about reducers and stuff aren't on topic for this discussion - if you've got other suggestions, please file an issue on the Redux repo. |
Ufff 🙂, this is really hard discussion… |
Hi. For a while i'm thinking for similar approach. Is there any example implementation like this? Thanks |
Maybe it's just me but I feel like the same use-cases enabled with this change to Wrapping components in nested The alternative of setting Consider the following example of the common pattern of using a global state singleton to populate context: import React, { createContext } from 'react';
const defaultContext = {
video: {
stream: undefined,
},
userData: {
id: 1,
name: 'Bob',
},
};
const Context = createContext(defaultContext);
export class Main extends React.PureComponent {
state = {};
constructor(props: any) {
super(props);
this.state = defaultContext;
}
async componentDidMount() {
const stream = await navigator.mediaDevices.getUserMedia({});
this.setState(
{
video: {
stream,
},
},
async () => {
// dummy fetch method, imagine it returns a user with ID and name
const userData = await fetchUserData();
this.setState({
userData,
});
},
);
}
render() {
return (
<Context.Provider value={this.state as any}>
<HelloWorld />
</Context.Provider>
);
}
}
export class HelloWorld extends React.PureComponent {
static contextType = Context;
context!: React.ContextType<typeof Context>;
shouldApplyContext(prev, next) {
return prev.userData.name !== next.userData.name;
}
render() {
return (
<div>
Hello, {this.context.userData.name} <Video />
</div>
);
}
}
export class Video extends React.PureComponent {
static contextType = Context;
context!: React.ContextType<typeof Context>;
ref: HTMLVideoElement;
shouldApplyContext(prev, next) {
return prev.video.stream.id !== next.video.stream.id;
}
componentDidMount() {
this.ref.srcObject = this.context.video.stream;
}
render() {
return <video ref={(el) => (this.ref = el)} />;
}
} Currently, both child components above would render multiple times due to the Having a singleton store that gets fractally consumed is a pattern/use-case that is common and trivial in MobX (because MobX observers subscribe to properties they consume instead of changes to the entire store), but does not work well when porting to the Context API. I haven't looked at how the internals of the Context API work around here, but I imagine it could be implemented by adjusting the code that applies changes to consumers to inspect the class of the given consumer for a My current real-life use-case is I have an app with a singleton context containing 3 properties: |
Closing in favor of #119 . |
Per a suggestion from Dan Abramov: this RFC proposes allowing a
Context.Provider
instance to accept acalculateChangedBits
function as a prop, in addition to the current implementation of accepting them as an argument toReact.createContext
.View rendered RFC