-
Notifications
You must be signed in to change notification settings - Fork 46.8k
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
ReactNative fiber renderer #8560
Conversation
c80ff3d
to
3d11b18
Compare
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.
https://github.com/facebook/react/blob/master/src/renderers/shared/fiber/ReactFiberBeginWork.js#L228
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
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 mv
ed 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 mv
ed 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.
} | ||
|
||
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.
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 toReactNativeStack
and a new flags fileReactNativeFeatureFlags
has been added to switch between the 2 renderers. (Other than injecting afindNodeHandle
strategyReactNativeStack
has 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
UIManager
with 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.getOwnerHierarchy
reaches into stack internals in a way that is not Fiber-compatible and causes a runtime error.Open question: How should we handle children?
ReactNativeFiber
currently 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 theHostConfig
methods:PassUpdate Already done via PR Drop runtime validation and lower case coercion of tag names #8563. Howevertype
to the following methods:commitUpdate
. (This is necessary to get theviewConfig
forvalidAttributes
anduiViewClassName
.)viewConfig
can't be removed for the time being becauseNativeMethodsMixin
depends on it.children
to the following methods:appendChild
,finalizeInitialChildren
,insertBefore
,removeChild
. (This is necessary to add/set/move/remove children.)RCTUIManager
errors with "Invalid view set to be the JS responder".)As for tracking the
children
array there are 2 options to consider:Option 1: Manage index information in Fiber
Fiber has enough information to pass the additional params to the
HostConfig
but it would need to calculate and/or store the child information in order to do so. The benefit provided toReactNativeFiber
may 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:
addSubview
forappendChild
insertSubview(_:aboveSubview:)
forinsertBefore
removeFromSuperview
forremoveChild
This would also need to be supported on the Android side, perhaps with the following methods:
addView
forappendChild
View
getZ
andsetZ
methods forinsertBefore
(should verify with someone more familiar with Android)removeView
forremoveChild