-
Notifications
You must be signed in to change notification settings - Fork 49.8k
ReactNative fiber renderer #8560
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
c80ff3d to
3d11b18
Compare
edvinerikson
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.
Looks good! I guess we need a way to cleanup native views.
https://twitter.com/sebmarkbage/status/799027829073727489
|
|
||
| const viewConfigs = new Map(); | ||
|
|
||
| const prefix = 'topsecret-'; |
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 guess this will bloat devtools a little bit if all host components are prefixed with this?
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.
yeah we should figure out what we actually want to do here. I think this can land first though.
| scheduleDeferredCallback: window.requestIdleCallback, | ||
|
|
||
| shouldSetTextContent(props : Props) : boolean { | ||
| return false; |
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.
Haven't checked the Fiber code for a while. Does this make it so that all text get it's own instance? <Text>Hej</Text> Did not get a text instance before since it's a single child.
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 actually know enough to answer that question. (Maybe someone who knows more about Fiber than me can chime in?)
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 to be the case actually. Kind of handy since you don't have to handle that special case in createInstance/commitUpdate.
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.
Yeah, let's change this so we have fewer views created.
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.
Actually I think this is correct as-is. Returning true from this method will create an RCText view with no visible text. Looking at the underlying react-native source, at least on the Java side, I think it is expected that such a text view should contain FlatTextShadowNode children.
If I update shouldSetTextContent to behave more like the DOM fiber renderer and return true for string/number children, then the apps I've been testing lose most of their visible text.
I believe that the current behavior (in this PR) of creating both RCTRawText and RCText views matches native stack behavior (eg <Text>string</Text> creates an RCTRawText view for the inner "string").
Please correct if you feel I'm mistaken here. I'm new so it's quite possible. 😄
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.
Yeah I am not sure how much overhead the fiber and child work have in this case either. It felt kind of awkward implementing it in my PR since it was basically duplicating the createTextInstance method.
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.
Agreed. From a renderer's perspective, this is something that could be made simpler to implement. Maybe it will change.
How about for this PR, I'll add a TODO comment to revisit this specific area? 😄
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.
Sounds good to me 😊
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.
If we wanted to avoid the extra Fiber then Fiber itself could have this logic to call createTextInstance directly. I don't think it would need to live in the renderer.
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.
Aye, that's what I was thinking by
From a renderer's perspective, this is something that could be made simpler to implement.
| // Noop? | ||
| }, | ||
|
|
||
| scheduleAnimationCallback: window.requestAnimationFrame, |
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.
Maybe it should be global instead of window (probably doesn't matter)?
| UIManager.manageChildren( | ||
| parentInstance._nativeTag, // containerTag | ||
| [], // moveFromIndices | ||
| [], // moveToIndices |
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.
If you are tired of forgetting the arguments I added flowtypings for this function in my PR https://github.com/facebook/react/pull/8361/files#diff-0480067610415f2abfec7a69ca45997eR37
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.
Great suggestion. I will update the flow types in this PR as well.
|
Also in case you missed it, there has been some discussions in #8361 |
182d45b to
abacd2f
Compare
sophiebits
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.
I think this is good and we can land it – just a few inlines.
|
|
||
| function uncacheNode(inst) { | ||
| var tag = inst._rootNodeID; | ||
| // TODO (bvaughn) Clean up once Stack is deprecated |
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.
Don't you always know at the caller whether this is stack or Fiber? If you're using this function still, let's split it up into two.
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.
Good catch. Yeah, this change can be reverted.
|
|
||
| type Container = number; | ||
| type Props = Object; | ||
| type Instance = any; |
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.
Can you add a real type here and try to get rid of uses of any?
| child : Instance | TextInstance, | ||
| beforeChild : Instance | TextInstance | ||
| ) : void { | ||
| const children = (parentInstance : any)._children; |
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 is unsound if it ever is the container. Can we at least throw intentionally so it's clearly unimplemented?
| }, | ||
|
|
||
| prepareForCommit() : void { | ||
| // Noop? |
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, this corresponds to the wrappers in ReactNativeReconcileTransaction and should be a noop.
| var ReactNativeComponentTree = require('ReactNativeComponentTree'); | ||
| var ReactNativeInjection = require('ReactNativeInjection'); | ||
| var ReactNativeStackInjection = require('ReactNativeStackInjection'); | ||
|
|
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.
extra blank line?
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 was also git mved from the pre-existing ReactNative.js but I'll delete the extra line! 😄
| */ | ||
| 'use strict'; | ||
|
|
||
| // Require ReactNativeDefaultInjection first for its side effects of setting up |
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.
inaccurate comment (you don't require it first, you don't require any module with that name even, and requiring it is no longer side-effectful because of the explicit .inject())
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 was git mved from the pre-existing ReactNative.js. The only modifications to this file was the @providesModule name and the 6-line findNodeHandle.injection calls.
I'll delete the comment though.
|
|
||
| const viewConfigs = new Map(); | ||
|
|
||
| const prefix = 'topsecret-'; |
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.
yeah we should figure out what we actually want to do here. I think this can land first though.
src/renderers/native/ReactNative.js
Outdated
| } | ||
|
|
||
| module.exports = ReactNative; | ||
| module.exports = ReactNativeFeatureFlags.useFiber |
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.
For now let's commit this as always exporting Stack so we can sync to RN and not bundle Fiber accidentally. You can make a mock for it like src/renderers/dom/mocks/ReactDOM.js and switch on it in scripts/jest/test-framework-setup.js so we get jest tests for it though.
| (findNodeHandle(this) : any), | ||
| this.viewConfig.uiViewClassName, | ||
| updatePayload | ||
| (updatePayload : any) |
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.
These casts smell funny to me. .create() returns ?Object, while updateView returns Object. Can you figure out if one of them is wrong or if we should call .updateView only conditionally? Same with createView elsewhere.
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. I didn't modify this code other than to add the type-cast. But you're right. It looks fishy.
ReactNativeAttributePayload.create may return null if no props have changed. So that return type is correct.
The Java implementation of updateView doesn't specify a @Nullable annotation for the incoming payload param, but the underlying UIImplementation class does check for null before doing anything with them.
I think the right thing to do here though, regardless, is to check for non-null before calling. I'll do that.
Actually I think the right thing to do is to change the flow type to annotate the property as nullable. Because the native implementation handles nullable props for both createView andupdateView functions.
c4b089b to
915264c
Compare
|
Would be nice to get rid of the viewConfig eventually. |
b5d9f3c to
5266ca9
Compare
This PR adds a new, fiber-based renderer for native dubbed
ReactNativeFiber. Existing native renderer has been renamed toReactNativeStackand a new flags fileReactNativeFeatureFlagshas been added to switch between the 2 renderers. (Other than injecting afindNodeHandlestrategyReactNativeStackhas not been modified.)I've done some basic smoke-testings with Android and iOS and the fiber renderer seems to work well with the following applications: Catalyst, Ads Manager, and Facebook "Events" tab.
I have also added/enhanced Flow types for
UIManagerwith this PR. If it's desirable for that work to be split into a separate PR, let me know and I can break it off.Known issues
InspectorUtils.getOwnerHierarchyreaches into stack internals in a way that is not Fiber-compatible and causes a runtime error.Open question: How should we handle children?
ReactNativeFibercurrently creates wrapper objects to bundle each native tag with its children andviewConfig. This creates one wrapper object per native view which may be undesirable. We could remove the wrapper object but we would need to pass some additional information to theHostConfigmethods:PassUpdate Already done via PR Drop runtime validation and lower case coercion of tag names #8563. Howevertypeto the following methods:commitUpdate. (This is necessary to get theviewConfigforvalidAttributesanduiViewClassName.)viewConfigcan't be removed for the time being becauseNativeMethodsMixindepends on it.childrento the following methods:appendChild,finalizeInitialChildren,insertBefore,removeChild. (This is necessary to add/set/move/remove children.)RCTUIManagererrors with "Invalid view set to be the JS responder".)As for tracking the
childrenarray there are 2 options to consider:Option 1: Manage index information in Fiber
Fiber has enough information to pass the additional params to the
HostConfigbut it would need to calculate and/or store the child information in order to do so. The benefit provided toReactNativeFibermay be outweighed by Fiber having to do this for other renderers that don't require the information.Option 2: Update native bridge
A better solution to this issue might be to change the iOS and Android bridges to move away from the index-based add/remove methods.
For example, iOS provides methods that could be used instead of our current index-based ones:
addSubviewforappendChildinsertSubview(_:aboveSubview:)forinsertBeforeremoveFromSuperviewforremoveChildThis would also need to be supported on the Android side, perhaps with the following methods:
addViewforappendChildViewgetZandsetZmethods forinsertBefore(should verify with someone more familiar with Android)removeViewforremoveChild