Skip to content

Migrate methods like setNativeProps off of refs. #72

@elicwhite

Description

@elicwhite

Context

React Native has native components like RCTView, RCTText, RCTImage, etc that are implemented in native code. When JS code tries to render them, it is done by rendering a string:

const RCTView = 'RCTView';
return <RCTView {...props} />;

React knows that components that are strings are special native components, vs functions or classes which are JS components. When these special native components are rendered, React creates "host" components in JS that add some additional behavior and metadata. This includes methods like blur, focus, measure, measureInWindow, measureLayout, and setNativeProps.

Problems

Every time a native component is rendered, React has to construct a wrapper class for this node that contains these methods because React doesn't know if these methods will be accessed on a ref of these components.

Creating these classes results in additional memory usage and runtime perf overhead. There are also a couple of different situations where these methods can be created which leads to a bunch of similar code, bloating the default React Native bundle. Some examples include NativeMethodsMixin, ReactFabricHostConfig.js, ReactNativeComponent.js, and ReactNativeFiberHostComponent.js.

Furthermore, there can be complexities when creating JS components that render to these native components, wanting to provide custom imperative methods while also providing access to the native component methods. For example, native components have focus and blur methods, but if the JS TextInput component wants to expose a clear method, it isn't able to forward the ref to the native component because that wouldn't include clear, and it can't not use forwardRef because then it wouldn't include focus and blur. This has led us to try to duplicate all the native methods in the JS component as passthrough methods to the underlying ref. Another common approach to this problem is by having an imperative method like getNativeRef(). This approach also isn't great because it leaks implementation details and is prone to breakages when refactoring. Definitely not ideal.

Proposal

Instead of having to create a wrapper class for every native component just in case one of these methods is called, we propose moving these methods to be an export from ReactNative. That means instead of calling the methods on the ref, you would pass the ref to the publicly exported functions.

Concretely, this would be an api change from calling setNativeProps like this:

refToNativeComponent.setNativeProps({text: ''});

to

import {setNativeProps} from 'react-native';
// ...
setNativeProps(refToNativeComponent, {text: ''});

The implementation of these methods would still be defined in the ReactNativeRenderer which is required because the implementation for Fabric is different than the current one for Paper. They would be exported from react-native's public API as a re-export from the renderer. This is currently what we do to export findNodeHandle

Thoughts on Implementation

I think we can make this migration incrementally by first creating the new versions of these methods that take a ref as an argument. We would want this implementation to be the source of truth for the methods instead of having these just call the method on the ref.

We can then change the NativeMethodsMixin and HostComponents to call the top level API. This also gives us a layer at which to add a deprecation warning to ask out of repo callsites to migrate.

Once we decide to finally remove the old implementation we can clean up the renderers removing the old approach and realize the performance and memory wins.

cc @sebmarkbage

Metadata

Metadata

Assignees

No one assigned

    Labels

    🗣 DiscussionThis label identifies an ongoing discussion on a subject

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions