Reduce re-rendering of Animated components in Fabric

Summary:
The `isFabric` method used in createAnimatedComponent is unreliable (another reason in a long list of reasons why you should not duplicate this code elsewhere, and why we want to delete it ASAP).
In particular, during the first render, the ref component has not been set yet, so we /cannot/ detect if the component is Fabric or non-Fabric and assume it's non-Fabric.

In Fabric, this causes `collapsable` and `nativeID` values to change after the first render.

To reduce this re-rendering, but not eliminate it for all components, I've introduced a flag that indicates if a component will /never/ be flattened. In particular, Image components, ScrollViews, Text cannot ever be flattened,
so we should always pass `collapsable:false` and the same nativeID prop for those components. For Animated <View>s and other components, the re-rendering issue is still a problem in Fabric for now.

Changelog: [Internal]

Reviewed By: mdvacca

Differential Revision: D25720322

fbshipit-source-id: fe3234d8ae974911a2b5f82e4f6a093216f43d4b
This commit is contained in:
Joshua Gross
2020-12-28 22:33:01 -08:00
committed by Facebook GitHub Bot
parent f002a5710f
commit b8ca59cd74
5 changed files with 36 additions and 21 deletions
@@ -17,9 +17,9 @@ const createAnimatedComponent = require('../createAnimatedComponent');
import type {AnimatedComponentType} from '../createAnimatedComponent';
module.exports = (createAnimatedComponent(
(Image: $FlowFixMe),
): AnimatedComponentType<
module.exports = (createAnimatedComponent((Image: $FlowFixMe), {
collapsable: false,
}): AnimatedComponentType<
React.ElementConfig<typeof Image>,
React.ElementRef<typeof Image>,
>);
@@ -24,9 +24,9 @@ const ScrollViewWithEventThrottle = React.forwardRef((props, ref) => (
<ScrollView scrollEventThrottle={0.0001} {...props} ref={ref} />
));
module.exports = (createAnimatedComponent(
ScrollViewWithEventThrottle,
): AnimatedComponentType<
module.exports = (createAnimatedComponent(ScrollViewWithEventThrottle, {
collapsable: false,
}): AnimatedComponentType<
React.ElementConfig<typeof ScrollView>,
React.ElementRef<typeof ScrollView>,
>);
@@ -17,9 +17,9 @@ const createAnimatedComponent = require('../createAnimatedComponent');
import type {AnimatedComponentType} from '../createAnimatedComponent';
module.exports = (createAnimatedComponent(
(Text: $FlowFixMe),
): AnimatedComponentType<
module.exports = (createAnimatedComponent((Text: $FlowFixMe), {
collapsable: false,
}): AnimatedComponentType<
React.ElementConfig<typeof Text>,
React.ElementRef<typeof Text>,
>);
@@ -17,7 +17,9 @@ const createAnimatedComponent = require('../createAnimatedComponent');
import type {AnimatedComponentType} from '../createAnimatedComponent';
module.exports = (createAnimatedComponent(View): AnimatedComponentType<
module.exports = (createAnimatedComponent(View, {
collapsable: true,
}): AnimatedComponentType<
React.ElementConfig<typeof View>,
React.ElementRef<typeof View>,
>);
+24 -11
View File
@@ -37,8 +37,13 @@ export type AnimatedComponentType<
Instance,
>;
type AnimatedComponentOptions = {
collapsable?: boolean,
};
function createAnimatedComponent<Props: {+[string]: mixed, ...}, Instance>(
Component: React.AbstractComponent<Props, Instance>,
options?: AnimatedComponentOptions,
): AnimatedComponentType<Props, Instance> {
invariant(
typeof Component !== 'function' ||
@@ -79,6 +84,9 @@ function createAnimatedComponent<Props: {+[string]: mixed, ...}, Instance>(
}
_isFabric = (): boolean => {
// When called during the first render, `_component` is always null.
// Therefore, even if a component is rendered in Fabric, we can't detect
// that until ref is set, which happens sometime after the first render.
if (this._component == null) {
return false;
}
@@ -213,23 +221,28 @@ function createAnimatedComponent<Props: {+[string]: mixed, ...}, Instance>(
const {style: passthruStyle = {}, ...passthruProps} =
this.props.passthroughAnimatedPropExplicitValues || {};
const mergedStyle = {...style, ...passthruStyle};
const forceNativeId =
props.collapsable ??
(this._propsAnimated.__isNative ||
this._isFabric() ||
options?.collapsable === false);
// The native driver updates views directly through the UI thread so we
// have to make sure the view doesn't get optimized away because it cannot
// go through the NativeViewHierarchyManager since it operates on the shadow
// thread. TODO: T68258846
const collapsableProps = forceNativeId
? {
nativeID: props.nativeID ?? 'animatedComponent',
collapsable: false,
}
: {};
return (
<Component
{...props}
{...passthruProps}
{...collapsableProps}
style={mergedStyle}
ref={this._setComponentRef}
nativeID={
props.nativeID ??
(this._isFabric() ? 'animatedComponent' : undefined)
} /* TODO: T68258846. */
// The native driver updates views directly through the UI thread so we
// have to make sure the view doesn't get optimized away because it cannot
// go through the NativeViewHierarchyManager since it operates on the shadow
// thread.
collapsable={
this._propsAnimated.__isNative ? false : props.collapsable
}
/>
);
}