Skip to content
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

Merged
merged 1 commit into from
Dec 15, 2016
Merged

Conversation

bvaughn
Copy link
Contributor

@bvaughn bvaughn commented Dec 12, 2016

This PR adds a new, fiber-based renderer for native dubbed ReactNativeFiber. Existing native renderer has been renamed to ReactNativeStack and a new flags file ReactNativeFeatureFlags has been added to switch between the 2 renderers. (Other than injecting a findNodeHandle strategy ReactNativeStack 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

  • The inspector does not work with the Fiber-based renderer. The react-native InspectorUtils.getOwnerHierarchy reaches into stack internals in a way that is not Fiber-compatible and causes a runtime error.
  • Testing various internal apps yields the following:
    • Facebook app Events page seems fine.
    • Catalyst seems mostly fine, but errors with "Unexpected context pop" when I open the "App - UIExplorer Browser" component.
    • Ads Manager errors with "Unexpected context pop" on some pages.

Open question: How should we handle children?

ReactNativeFiber currently creates wrapper objects to bundle each native tag with its children and viewConfig. 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 the HostConfig methods:

  • Pass type to the following methods: commitUpdate. (This is necessary to get the viewConfig for validAttributes and uiViewClassName.) Update Already done via PR Drop runtime validation and lower case coercion of tag names #8563. However viewConfig can't be removed for the time being because NativeMethodsMixin depends on it.
  • Pass children to the following methods: appendChild, finalizeInitialChildren, insertBefore, removeChild. (This is necessary to add/set/move/remove children.)
  • Provide a hook to recursively call remove children (so that we can uncache all child nodes). (Without this the 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 to ReactNativeFiber 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:

This would also need to be supported on the Android side, perhaps with the following methods:

Copy link
Contributor

@edvinerikson edvinerikson left a 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-';
Copy link
Contributor

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?

Copy link
Collaborator

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;
Copy link
Contributor

@edvinerikson edvinerikson Dec 13, 2016

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.

Copy link
Contributor Author

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?)

Copy link
Contributor

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

Copy link
Collaborator

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.

Copy link
Contributor Author

@bvaughn bvaughn Dec 14, 2016

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. 😄

Copy link
Contributor

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.

Copy link
Contributor Author

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? 😄

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sounds good to me 😊

Copy link
Collaborator

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.

Copy link
Contributor Author

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,
Copy link
Contributor

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
Copy link
Contributor

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

Copy link
Contributor Author

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.

@edvinerikson
Copy link
Contributor

Also in case you missed it, there has been some discussions in #8361

Copy link
Collaborator

@sophiebits sophiebits left a 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
Copy link
Collaborator

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.

Copy link
Contributor Author

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;
Copy link
Collaborator

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;
Copy link
Collaborator

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?
Copy link
Collaborator

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');

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extra blank line?

Copy link
Contributor Author

@bvaughn bvaughn Dec 14, 2016

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
Copy link
Collaborator

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())

Copy link
Contributor Author

@bvaughn bvaughn Dec 14, 2016

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-';
Copy link
Collaborator

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
Copy link
Collaborator

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)
Copy link
Collaborator

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.

Copy link
Contributor Author

@bvaughn bvaughn Dec 14, 2016

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.

@sophiebits
Copy link
Collaborator

Would be nice to get rid of the viewConfig eventually.

@bvaughn bvaughn merged commit ab72f62 into facebook:master Dec 15, 2016
@bvaughn bvaughn deleted the react-native-fiber branch December 15, 2016 04:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants