Pass unflattened styles to reconciler (#45345)

Summary:
Pull Request resolved: https://github.com/facebook/react-native/pull/45345

When React diffs props, it can can short-circuit nested objects if their object identity hasn't changed. Whenever we use `flattenStyle` we prevent this optimization from taking place.

Changelog: [Internal]

Reviewed By: dmytrorykun

Differential Revision: D59518281

fbshipit-source-id: e88ca781ab4622b5342169f8f27b09f0515513b3
This commit is contained in:
Pieter De Baets
2024-08-12 08:13:17 -07:00
committed by Facebook GitHub Bot
parent 7e41ea4c9d
commit d9d638c06b
7 changed files with 67 additions and 68 deletions
@@ -9,6 +9,7 @@
*/
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes';
import type {
PressEvent,
ScrollEvent,
@@ -1534,10 +1535,18 @@ function InternalTextInput(props: Props): React.Node {
};
}
let style = flattenStyle<TextStyleProp>(props.style);
if (typeof style?.fontWeight === 'number') {
// $FlowFixMe
style = [style, {fontWeight: style.fontWeight.toString()}];
// Keep the original (potentially nested) style when possible, as React can diff these more efficiently
let _style = props.style;
const flattenedStyle = flattenStyle<TextStyleProp>(props.style);
if (typeof flattenedStyle?.fontWeight === 'number') {
_style = [
_style,
{
fontWeight:
// $FlowFixMe[incompatible-cast]
(flattenedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']),
},
];
}
if (Platform.OS === 'ios') {
@@ -1548,10 +1557,10 @@ function InternalTextInput(props: Props): React.Node {
const useMultilineDefaultStyle =
props.multiline === true &&
(style == null ||
(style.padding == null &&
style.paddingVertical == null &&
style.paddingTop == null));
(flattenedStyle == null ||
(flattenedStyle.padding == null &&
flattenedStyle.paddingVertical == null &&
flattenedStyle.paddingTop == null));
textInput = (
<RCTTextInputView
@@ -1578,7 +1587,7 @@ function InternalTextInput(props: Props): React.Node {
selectionColor={selectionColor}
style={StyleSheet.compose(
useMultilineDefaultStyle ? styles.multilineDefault : null,
style,
_style,
)}
text={text}
/>
@@ -1645,7 +1654,7 @@ function InternalTextInput(props: Props): React.Node {
onScroll={_onScroll}
onSelectionChange={_onSelectionChange}
placeholder={placeholder}
style={style}
style={_style}
text={text}
textBreakStrategy={props.textBreakStrategy}
/>
@@ -150,24 +150,20 @@ let BaseImage: AbstractImageAndroid = React.forwardRef(
);
}
let style;
let style: ImageStyleProp;
let sources;
if (Array.isArray(source)) {
style = flattenStyle<ImageStyleProp>([styles.base, props.style]);
style = [styles.base, props.style];
sources = source;
} else {
const {uri} = source;
const width = source.width ?? props.width;
const height = source.height ?? props.height;
style = flattenStyle<ImageStyleProp>([
{width, height},
styles.base,
props.style,
]);
sources = [source];
if (uri === '') {
console.warn('source.uri should not be an empty string');
}
const width = source.width ?? props.width;
const height = source.height ?? props.height;
style = [{width, height}, styles.base, props.style];
sources = [source];
}
const {height, width, ...restProps} = props;
@@ -203,11 +199,10 @@ let BaseImage: AbstractImageAndroid = React.forwardRef(
},
};
const objectFit = style?.objectFit
? convertObjectFitToResizeMode(style.objectFit)
: null;
const flattenedStyle = flattenStyle<ImageStyleProp>(style);
const objectFit = convertObjectFitToResizeMode(flattenedStyle?.objectFit);
const resizeMode =
objectFit || props.resizeMode || style?.resizeMode || 'cover';
objectFit || props.resizeMode || flattenedStyle?.resizeMode || 'cover';
const actualRef = useWrapRefWithImageAttachedCallbacks(forwardedRef);
@@ -8,7 +8,7 @@
* @format
*/
import type {ImageStyle, ImageStyleProp} from '../StyleSheet/StyleSheet';
import type {ImageStyleProp} from '../StyleSheet/StyleSheet';
import type {RootTag} from '../Types/RootTagTypes';
import type {AbstractImageIOS, ImageIOS} from './ImageTypes.flow';
import type {ImageSize} from './NativeImageLoaderAndroid';
@@ -112,38 +112,27 @@ let BaseImage: AbstractImageIOS = React.forwardRef((props, forwardedRef) => {
height: undefined,
};
let style: ImageStyleProp;
let sources;
let style: ImageStyle;
if (Array.isArray(source)) {
style =
flattenStyle<ImageStyleProp>([styles.base, props.style]) ||
({}: ImageStyle);
style = [styles.base, props.style];
sources = source;
} else {
const {uri} = source;
const width = source.width ?? props.width;
const height = source.height ?? props.height;
style =
flattenStyle<ImageStyleProp>([
{width, height},
styles.base,
props.style,
]) || ({}: ImageStyle);
sources = [source];
if (uri === '') {
console.warn('source.uri should not be an empty string');
}
const width = source.width ?? props.width;
const height = source.height ?? props.height;
style = [{width, height}, styles.base, props.style];
sources = [source];
}
const objectFit =
style.objectFit != null
? convertObjectFitToResizeMode(style.objectFit)
: null;
const flattenedStyle = flattenStyle<ImageStyleProp>(style);
const objectFit = convertObjectFitToResizeMode(flattenedStyle?.objectFit);
const resizeMode =
objectFit || props.resizeMode || style.resizeMode || 'cover';
const tintColor = props.tintColor ?? style.tintColor;
objectFit || props.resizeMode || flattenedStyle?.resizeMode || 'cover';
const tintColor = props.tintColor ?? flattenedStyle?.tintColor;
if (props.children != null) {
throw new Error(
+9 -9
View File
@@ -10,13 +10,13 @@
type ResizeMode = 'cover' | 'contain' | 'stretch' | 'repeat' | 'center';
export function convertObjectFitToResizeMode(objectFit: string): ResizeMode {
const objectFitMap = {
contain: 'contain',
cover: 'cover',
fill: 'stretch',
'scale-down': 'contain',
};
// $FlowFixMe[invalid-computed-prop]
return objectFitMap[objectFit];
const objectFitMap: {[string]: ResizeMode} = {
contain: 'contain',
cover: 'cover',
fill: 'stretch',
'scale-down': 'contain',
};
export function convertObjectFitToResizeMode(objectFit: ?string): ?ResizeMode {
return objectFit != null ? objectFitMap[objectFit] : undefined;
}
@@ -31,11 +31,16 @@ exports[`Image should render as <RCTImageView> when not mocked 1`] = `
]
}
style={
Object {
"height": undefined,
"overflow": "hidden",
"width": undefined,
}
Array [
Object {
"height": undefined,
"width": undefined,
},
Object {
"overflow": "hidden",
},
undefined,
]
}
/>
`;
+7 -6
View File
@@ -8,6 +8,7 @@
* @format
*/
import type {TextStyleProp} from '../StyleSheet/StyleSheet';
import type {____TextStyle_Internal as TextStyleInternal} from '../StyleSheet/StyleSheetTypes';
import type {PressEvent} from '../Types/CoreEventTypes';
import type {NativeTextProps} from './TextNativeComponent';
@@ -133,7 +134,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
let _selectable = selectable;
let processedStyle: ?TextStyleInternal = flattenStyle(_style);
let processedStyle = flattenStyle<TextStyleProp>(_style);
if (processedStyle != null) {
let overrides: ?{...TextStyleInternal} = null;
if (typeof processedStyle.fontWeight === 'number') {
@@ -158,7 +159,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
if (overrides != null) {
// $FlowFixMe[incompatible-type]
processedStyle = [processedStyle, overrides];
_style = [_style, overrides];
}
}
@@ -178,7 +179,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: processedStyle,
style: _style,
disabled: disabled,
children,
}}
@@ -212,7 +213,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={processedStyle}
style={_style}
disabled={disabled}>
{children}
</NativeVirtualText>
@@ -256,7 +257,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
numberOfLines: _numberOfLines,
selectable: _selectable,
selectionColor: _selectionColor,
style: processedStyle,
style: _style,
children,
}}
textPressabilityProps={{
@@ -291,7 +292,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
ref={forwardedRef}
selectable={_selectable}
selectionColor={_selectionColor}
style={processedStyle}>
style={_style}>
{children}
</NativeText>
);
@@ -5002,8 +5002,8 @@ export type { ImageProps } from \\"./ImageProps\\";
exports[`public API should not change unintentionally Libraries/Image/ImageUtils.js 1`] = `
"type ResizeMode = \\"cover\\" | \\"contain\\" | \\"stretch\\" | \\"repeat\\" | \\"center\\";
declare export function convertObjectFitToResizeMode(
objectFit: string
): ResizeMode;
objectFit: ?string
): ?ResizeMode;
"
`;