-
Notifications
You must be signed in to change notification settings - Fork 50.4k
Support Promise as a renderable node #25634
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
Conversation
ca01d51 to
324cf1f
Compare
a665a00 to
e593439
Compare
e593439 to
b494eb5
Compare
| lanes: Lanes, | ||
| ): Fiber | null { | ||
| // This indirection only exists so we can reset `thenableState` at the end. | ||
| // It should get inlined by Closure. |
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.
This doesn't appear to be the case. This function is called in many places and it's larger than closure considers inlinable. reconcileChildFibersImpl is returned as first class so it can't be inlined and is also recursive, so it's also not inlined into this function.
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.
Hmm maybe it would work if I move the indirection into the factory function
I could also submit a PR to get rid of createChildReconciler and thread the arguments through instead
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.
It makes sense to me that reconcileChildFibersImpl wouldn't get inlined, since it's recursive. Do you have a guess for why the outer one (reconcileChildFibers) isn't inlined into its callers, though? Since it's only a few lines long.
b494eb5 to
7f46ef1
Compare
sebmarkbage
left a comment
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.
Seems like we need to refactor more in this space in follow ups anyway.
Just a rename. Scope of this test file has expanded.
Implements Promise as a valid React node types. The idea is that any type that can be unwrapped with `use` should also be renderable. When the reconciler encounters a Usable in a child position, it will transparently unwrap the value before reconciling it. The value of the inner value will determine the identity of the child during reconciliation, not the Usable object that wraps around it. Unlike `use`, the reconciler will recursively unwrap the value until it reaches a non-Usable type, e.g. Usable<Usable<Usable<T>>> will resolve to T. In this initial commit, I've added support for Promises. I will do Context in the next step. Being able to render a promise as a child has several interesting implications. The Server Components response format can use this feature in its implementation — instead of wrapping references to client components in `React.lazy`, it can just use a promise. This also fulfills one of the requirements for async components on the client, because an async component always returns a promise for a React node. However, we will likely warn and/or lint against this for the time being because there are major caveats if you re-render an async component in response to user input. (Note: async components already work in a Server Components environment — the caveats only apply to running them in the browser.) To suspend, React uses the same algorithm as `use`: by throwing an exception to unwind the stack, then replaying the begin phase once the promise resolves. It's a little weird to suspend during reconciliation, however, `lazy` already does this so if there were any obvious bugs related to that we likely would have already found them. Still, the structure is a bit unfortunate. Ideally, we shouldn't need to replay the entire begin phase of the parent fiber in order to reconcile the children again. This would require a somewhat significant refactor, because reconciliation happens deep within the begin phase, and depending on the type of work, not always at the end. We should consider as a future improvement.
7f46ef1 to
610b9df
Compare
|
Comparing: f411e89...610b9df Critical size changesIncludes critical production bundles, as well as any change greater than 2%:
Significant size changesIncludes any change greater than 0.2%: Expand to show
|
Implements Promise as a valid React node types. The idea is that any type that can be unwrapped with
useshould also be renderable.When the reconciler encounters a Usable in a child position, it will transparently unwrap the value before reconciling it. The value of the inner value will determine the identity of the child during reconciliation, not the Usable object that wraps around it.
Unlike
use, the reconciler will recursively unwrap the value until it reaches a non-Usable type, e.g.Usable<Usable<Usable<T>>>will resolve to T.In this initial commit, I've added support for Promises. I will do Context in the next step.
Being able to render a promise as a child has several interesting implications. The Server Components response format can use this feature in its implementation — instead of wrapping references to client components in
React.lazy, it can just use a promise.This also fulfills one of the requirements for async components on the client, because an async component always returns a promise for a React node. However, we will likely warn and/or lint against this for the time being because there are major caveats if you re-render an async component in response to user input. (Note: async components already work in a Server Components environment — the caveats only apply to running them in the browser.)
To suspend, React uses the same algorithm as
use: by throwing an exception to unwind the stack, then replaying the begin phase once the promise resolves. It's a little weird to suspend during reconciliation, however,lazyalready does this so if there were any obvious bugs related to that we likely would have already found them.Still, the structure is a bit unfortunate. Ideally, we shouldn't need to replay the entire begin phase of the parent fiber in order to reconcile the children again. This would require a somewhat significant refactor, because reconciliation happens deep within the begin phase, and depending on the type of work, not always at the end. We should consider as a future improvement.