mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
d8c25ca1b6
Summary: D36902220 (https://github.com/facebook/react-native/commit/a04195167bbd8f27c6141c0239a61a345cac5a88) changed Animated to only use value of natively driven nodes on initial render. However, there remained a case where we could end up with a race condition between Fabric prop update (via SurfaceMountingManager.updateProps) and Animated (via NativeAnimatedNodesManager.runUpdates), when an animation on a node that was created natively is triggered close to render (such as in componentDidUpdate). This happens as Animated and Fabric aren't synchronized, and at the platform level, they do not know each other's state. Say we have two items, where opacity is used to indicate whether the item is selected. On initial render, A's opacity is set to 1, and animated sets opacity to 1; B's opacity is set to 0, and animated sets opacity to 0. When B is selected (and causes A and B to rerender), A's opacity is now set to null, and animated sets opacity to 0; B's opacity is also now set to null, and animated sets opacity to 1. A's props have changed, and thus the default opacity value of 1 is applied via Fabric, but Animated also sets the opacity to 0 - either may end up being the value visible to the user due to the nondeterministic order of Fabric update props and Animated. This is what is causing T122469354. This diff addresses this edge case by using the initial prop values for native animated nodes, for subsequent renders, to ensure that values of native animated nodes do not impact rerenders. This diff also fixes a bug in OCAnimation where translateX/Y values of 0 in transition will result in render props containing translateX/Y instead of transform, resulting in potentially incorrect pressability bounds. Changelog: [Internal][Fixed] - Only use initial value of natively driven nodes on render Reviewed By: JoshuaGross, javache Differential Revision: D36958882 fbshipit-source-id: 10be2ad91b645fa4b8a4a12808e9299da33aaf7d
133 lines
4.2 KiB
JavaScript
133 lines
4.2 KiB
JavaScript
/**
|
|
* Copyright (c) Meta Platforms, Inc. and affiliates.
|
|
*
|
|
* This source code is licensed under the MIT license found in the
|
|
* LICENSE file in the root directory of this source tree.
|
|
*
|
|
* @flow
|
|
* @format
|
|
*/
|
|
|
|
'use strict';
|
|
|
|
import type {PlatformConfig} from '../AnimatedPlatformConfig';
|
|
|
|
const flattenStyle = require('../../StyleSheet/flattenStyle');
|
|
const NativeAnimatedHelper = require('../NativeAnimatedHelper');
|
|
const AnimatedNode = require('./AnimatedNode');
|
|
const AnimatedTransform = require('./AnimatedTransform');
|
|
const AnimatedWithChildren = require('./AnimatedWithChildren');
|
|
|
|
class AnimatedStyle extends AnimatedWithChildren {
|
|
_style: Object;
|
|
|
|
constructor(style: any) {
|
|
super();
|
|
style = flattenStyle(style) || ({}: {[string]: any});
|
|
if (style.transform) {
|
|
style = {
|
|
...style,
|
|
transform: new AnimatedTransform(style.transform),
|
|
};
|
|
}
|
|
this._style = style;
|
|
}
|
|
|
|
// Recursively get values for nested styles (like iOS's shadowOffset)
|
|
_walkStyleAndGetValues(style: any, initialStyle: ?Object) {
|
|
const updatedStyle: {[string]: any | {...}} = {};
|
|
for (const key in style) {
|
|
const value = style[key];
|
|
if (value instanceof AnimatedNode) {
|
|
// During initial render we want to use the initial value of both natively and non-natively
|
|
// driven nodes. On subsequent renders, we cannot use the value of natively driven nodes
|
|
// as they may not be up to date, so we use the initial value to ensure that values of
|
|
// native animated nodes do not impact rerenders.
|
|
if (!initialStyle || !value.__isNative) {
|
|
updatedStyle[key] = value.__getValue();
|
|
} else if (initialStyle.hasOwnProperty(key)) {
|
|
updatedStyle[key] = initialStyle[key];
|
|
}
|
|
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
|
// Support animating nested values (for example: shadowOffset.height)
|
|
updatedStyle[key] = this._walkStyleAndGetValues(value, initialStyle);
|
|
} else {
|
|
updatedStyle[key] = value;
|
|
}
|
|
}
|
|
return updatedStyle;
|
|
}
|
|
|
|
__getValue(initialStyle: ?Object): Object {
|
|
return this._walkStyleAndGetValues(this._style, initialStyle);
|
|
}
|
|
|
|
// Recursively get animated values for nested styles (like iOS's shadowOffset)
|
|
_walkStyleAndGetAnimatedValues(style: any) {
|
|
const updatedStyle: {[string]: any | {...}} = {};
|
|
for (const key in style) {
|
|
const value = style[key];
|
|
if (value instanceof AnimatedNode) {
|
|
updatedStyle[key] = value.__getAnimatedValue();
|
|
} else if (value && !Array.isArray(value) && typeof value === 'object') {
|
|
// Support animating nested values (for example: shadowOffset.height)
|
|
updatedStyle[key] = this._walkStyleAndGetAnimatedValues(value);
|
|
}
|
|
}
|
|
return updatedStyle;
|
|
}
|
|
|
|
__getAnimatedValue(): Object {
|
|
return this._walkStyleAndGetAnimatedValues(this._style);
|
|
}
|
|
|
|
__attach(): void {
|
|
for (const key in this._style) {
|
|
const value = this._style[key];
|
|
if (value instanceof AnimatedNode) {
|
|
value.__addChild(this);
|
|
}
|
|
}
|
|
}
|
|
|
|
__detach(): void {
|
|
for (const key in this._style) {
|
|
const value = this._style[key];
|
|
if (value instanceof AnimatedNode) {
|
|
value.__removeChild(this);
|
|
}
|
|
}
|
|
super.__detach();
|
|
}
|
|
|
|
__makeNative(platformConfig: ?PlatformConfig) {
|
|
for (const key in this._style) {
|
|
const value = this._style[key];
|
|
if (value instanceof AnimatedNode) {
|
|
value.__makeNative(platformConfig);
|
|
}
|
|
}
|
|
super.__makeNative(platformConfig);
|
|
}
|
|
|
|
__getNativeConfig(): Object {
|
|
const styleConfig: {[string]: ?number} = {};
|
|
for (const styleKey in this._style) {
|
|
if (this._style[styleKey] instanceof AnimatedNode) {
|
|
const style = this._style[styleKey];
|
|
style.__makeNative(this.__getPlatformConfig());
|
|
styleConfig[styleKey] = style.__getNativeTag();
|
|
}
|
|
// Non-animated styles are set using `setNativeProps`, no need
|
|
// to pass those as a part of the node config
|
|
}
|
|
NativeAnimatedHelper.validateStyles(styleConfig);
|
|
return {
|
|
type: 'style',
|
|
style: styleConfig,
|
|
};
|
|
}
|
|
}
|
|
|
|
module.exports = AnimatedStyle;
|