Skip to content

Commit d9d638c

Browse files
javachefacebook-github-bot
authored andcommitted
Pass unflattened styles to reconciler (#45345)
Summary: Pull Request resolved: #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
1 parent 7e41ea4 commit d9d638c

File tree

7 files changed

+67
-68
lines changed

7 files changed

+67
-68
lines changed

packages/react-native/Libraries/Components/TextInput/TextInput.js

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
*/
1010

1111
import type {HostComponent} from '../../Renderer/shims/ReactNativeTypes';
12+
import type {____TextStyle_Internal as TextStyleInternal} from '../../StyleSheet/StyleSheetTypes';
1213
import type {
1314
PressEvent,
1415
ScrollEvent,
@@ -1534,10 +1535,18 @@ function InternalTextInput(props: Props): React.Node {
15341535
};
15351536
}
15361537

1537-
let style = flattenStyle<TextStyleProp>(props.style);
1538-
if (typeof style?.fontWeight === 'number') {
1539-
// $FlowFixMe
1540-
style = [style, {fontWeight: style.fontWeight.toString()}];
1538+
// Keep the original (potentially nested) style when possible, as React can diff these more efficiently
1539+
let _style = props.style;
1540+
const flattenedStyle = flattenStyle<TextStyleProp>(props.style);
1541+
if (typeof flattenedStyle?.fontWeight === 'number') {
1542+
_style = [
1543+
_style,
1544+
{
1545+
fontWeight:
1546+
// $FlowFixMe[incompatible-cast]
1547+
(flattenedStyle.fontWeight.toString(): TextStyleInternal['fontWeight']),
1548+
},
1549+
];
15411550
}
15421551

15431552
if (Platform.OS === 'ios') {
@@ -1548,10 +1557,10 @@ function InternalTextInput(props: Props): React.Node {
15481557

15491558
const useMultilineDefaultStyle =
15501559
props.multiline === true &&
1551-
(style == null ||
1552-
(style.padding == null &&
1553-
style.paddingVertical == null &&
1554-
style.paddingTop == null));
1560+
(flattenedStyle == null ||
1561+
(flattenedStyle.padding == null &&
1562+
flattenedStyle.paddingVertical == null &&
1563+
flattenedStyle.paddingTop == null));
15551564

15561565
textInput = (
15571566
<RCTTextInputView
@@ -1578,7 +1587,7 @@ function InternalTextInput(props: Props): React.Node {
15781587
selectionColor={selectionColor}
15791588
style={StyleSheet.compose(
15801589
useMultilineDefaultStyle ? styles.multilineDefault : null,
1581-
style,
1590+
_style,
15821591
)}
15831592
text={text}
15841593
/>
@@ -1645,7 +1654,7 @@ function InternalTextInput(props: Props): React.Node {
16451654
onScroll={_onScroll}
16461655
onSelectionChange={_onSelectionChange}
16471656
placeholder={placeholder}
1648-
style={style}
1657+
style={_style}
16491658
text={text}
16501659
textBreakStrategy={props.textBreakStrategy}
16511660
/>

packages/react-native/Libraries/Image/Image.android.js

Lines changed: 9 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -150,24 +150,20 @@ let BaseImage: AbstractImageAndroid = React.forwardRef(
150150
);
151151
}
152152

153-
let style;
153+
let style: ImageStyleProp;
154154
let sources;
155155
if (Array.isArray(source)) {
156-
style = flattenStyle<ImageStyleProp>([styles.base, props.style]);
156+
style = [styles.base, props.style];
157157
sources = source;
158158
} else {
159159
const {uri} = source;
160-
const width = source.width ?? props.width;
161-
const height = source.height ?? props.height;
162-
style = flattenStyle<ImageStyleProp>([
163-
{width, height},
164-
styles.base,
165-
props.style,
166-
]);
167-
sources = [source];
168160
if (uri === '') {
169161
console.warn('source.uri should not be an empty string');
170162
}
163+
const width = source.width ?? props.width;
164+
const height = source.height ?? props.height;
165+
style = [{width, height}, styles.base, props.style];
166+
sources = [source];
171167
}
172168

173169
const {height, width, ...restProps} = props;
@@ -203,11 +199,10 @@ let BaseImage: AbstractImageAndroid = React.forwardRef(
203199
},
204200
};
205201

206-
const objectFit = style?.objectFit
207-
? convertObjectFitToResizeMode(style.objectFit)
208-
: null;
202+
const flattenedStyle = flattenStyle<ImageStyleProp>(style);
203+
const objectFit = convertObjectFitToResizeMode(flattenedStyle?.objectFit);
209204
const resizeMode =
210-
objectFit || props.resizeMode || style?.resizeMode || 'cover';
205+
objectFit || props.resizeMode || flattenedStyle?.resizeMode || 'cover';
211206

212207
const actualRef = useWrapRefWithImageAttachedCallbacks(forwardedRef);
213208

packages/react-native/Libraries/Image/Image.ios.js

Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
* @format
99
*/
1010

11-
import type {ImageStyle, ImageStyleProp} from '../StyleSheet/StyleSheet';
11+
import type {ImageStyleProp} from '../StyleSheet/StyleSheet';
1212
import type {RootTag} from '../Types/RootTagTypes';
1313
import type {AbstractImageIOS, ImageIOS} from './ImageTypes.flow';
1414
import type {ImageSize} from './NativeImageLoaderAndroid';
@@ -112,38 +112,27 @@ let BaseImage: AbstractImageIOS = React.forwardRef((props, forwardedRef) => {
112112
height: undefined,
113113
};
114114

115+
let style: ImageStyleProp;
115116
let sources;
116-
let style: ImageStyle;
117-
118117
if (Array.isArray(source)) {
119-
style =
120-
flattenStyle<ImageStyleProp>([styles.base, props.style]) ||
121-
({}: ImageStyle);
118+
style = [styles.base, props.style];
122119
sources = source;
123120
} else {
124121
const {uri} = source;
125-
const width = source.width ?? props.width;
126-
const height = source.height ?? props.height;
127-
style =
128-
flattenStyle<ImageStyleProp>([
129-
{width, height},
130-
styles.base,
131-
props.style,
132-
]) || ({}: ImageStyle);
133-
sources = [source];
134-
135122
if (uri === '') {
136123
console.warn('source.uri should not be an empty string');
137124
}
125+
const width = source.width ?? props.width;
126+
const height = source.height ?? props.height;
127+
style = [{width, height}, styles.base, props.style];
128+
sources = [source];
138129
}
139130

140-
const objectFit =
141-
style.objectFit != null
142-
? convertObjectFitToResizeMode(style.objectFit)
143-
: null;
131+
const flattenedStyle = flattenStyle<ImageStyleProp>(style);
132+
const objectFit = convertObjectFitToResizeMode(flattenedStyle?.objectFit);
144133
const resizeMode =
145-
objectFit || props.resizeMode || style.resizeMode || 'cover';
146-
const tintColor = props.tintColor ?? style.tintColor;
134+
objectFit || props.resizeMode || flattenedStyle?.resizeMode || 'cover';
135+
const tintColor = props.tintColor ?? flattenedStyle?.tintColor;
147136

148137
if (props.children != null) {
149138
throw new Error(

packages/react-native/Libraries/Image/ImageUtils.js

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -10,13 +10,13 @@
1010

1111
type ResizeMode = 'cover' | 'contain' | 'stretch' | 'repeat' | 'center';
1212

13-
export function convertObjectFitToResizeMode(objectFit: string): ResizeMode {
14-
const objectFitMap = {
15-
contain: 'contain',
16-
cover: 'cover',
17-
fill: 'stretch',
18-
'scale-down': 'contain',
19-
};
20-
// $FlowFixMe[invalid-computed-prop]
21-
return objectFitMap[objectFit];
13+
const objectFitMap: {[string]: ResizeMode} = {
14+
contain: 'contain',
15+
cover: 'cover',
16+
fill: 'stretch',
17+
'scale-down': 'contain',
18+
};
19+
20+
export function convertObjectFitToResizeMode(objectFit: ?string): ?ResizeMode {
21+
return objectFit != null ? objectFitMap[objectFit] : undefined;
2222
}

packages/react-native/Libraries/Image/__tests__/__snapshots__/Image-test.js.snap

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,11 +31,16 @@ exports[`Image should render as <RCTImageView> when not mocked 1`] = `
3131
]
3232
}
3333
style={
34-
Object {
35-
"height": undefined,
36-
"overflow": "hidden",
37-
"width": undefined,
38-
}
34+
Array [
35+
Object {
36+
"height": undefined,
37+
"width": undefined,
38+
},
39+
Object {
40+
"overflow": "hidden",
41+
},
42+
undefined,
43+
]
3944
}
4045
/>
4146
`;

packages/react-native/Libraries/Text/Text.js

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
* @format
99
*/
1010

11+
import type {TextStyleProp} from '../StyleSheet/StyleSheet';
1112
import type {____TextStyle_Internal as TextStyleInternal} from '../StyleSheet/StyleSheetTypes';
1213
import type {PressEvent} from '../Types/CoreEventTypes';
1314
import type {NativeTextProps} from './TextNativeComponent';
@@ -133,7 +134,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
133134

134135
let _selectable = selectable;
135136

136-
let processedStyle: ?TextStyleInternal = flattenStyle(_style);
137+
let processedStyle = flattenStyle<TextStyleProp>(_style);
137138
if (processedStyle != null) {
138139
let overrides: ?{...TextStyleInternal} = null;
139140
if (typeof processedStyle.fontWeight === 'number') {
@@ -158,7 +159,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
158159

159160
if (overrides != null) {
160161
// $FlowFixMe[incompatible-type]
161-
processedStyle = [processedStyle, overrides];
162+
_style = [_style, overrides];
162163
}
163164
}
164165

@@ -178,7 +179,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
178179
numberOfLines: _numberOfLines,
179180
selectable: _selectable,
180181
selectionColor: _selectionColor,
181-
style: processedStyle,
182+
style: _style,
182183
disabled: disabled,
183184
children,
184185
}}
@@ -212,7 +213,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
212213
ref={forwardedRef}
213214
selectable={_selectable}
214215
selectionColor={_selectionColor}
215-
style={processedStyle}
216+
style={_style}
216217
disabled={disabled}>
217218
{children}
218219
</NativeVirtualText>
@@ -256,7 +257,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
256257
numberOfLines: _numberOfLines,
257258
selectable: _selectable,
258259
selectionColor: _selectionColor,
259-
style: processedStyle,
260+
style: _style,
260261
children,
261262
}}
262263
textPressabilityProps={{
@@ -291,7 +292,7 @@ const Text: React.AbstractComponent<TextProps, TextForwardRef> =
291292
ref={forwardedRef}
292293
selectable={_selectable}
293294
selectionColor={_selectionColor}
294-
style={processedStyle}>
295+
style={_style}>
295296
{children}
296297
</NativeText>
297298
);

packages/react-native/Libraries/__tests__/__snapshots__/public-api-test.js.snap

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5002,8 +5002,8 @@ export type { ImageProps } from \\"./ImageProps\\";
50025002
exports[`public API should not change unintentionally Libraries/Image/ImageUtils.js 1`] = `
50035003
"type ResizeMode = \\"cover\\" | \\"contain\\" | \\"stretch\\" | \\"repeat\\" | \\"center\\";
50045004
declare export function convertObjectFitToResizeMode(
5005-
objectFit: string
5006-
): ResizeMode;
5005+
objectFit: ?string
5006+
): ?ResizeMode;
50075007
"
50085008
`;
50095009

0 commit comments

Comments
 (0)