Commit d26f7b3
Optimize flattenStyle for nested style arrays (#57203)
Summary:
- Walk nested style arrays directly into one result object instead of recursively allocating intermediate flattened objects.
- Preserve existing behavior for object styles, falsy entries, and later-style override order.
- Add focused unit coverage for array inputs that must still allocate merged result objects.
## Benchmark proof
External reproducible benchmark app:
https://github.com/tarikfp/rn-style-flatten-benchmark
Latest `yarn bench:compare` from the benchmark repo compares React Native `main` at `066c0d8bd8` against this branch at `81b5bc26b6`:
| scenario | before median ms | after median ms | change |
| --- | ---: | ---: | ---: |
| nested single style array | 294.79 | 98.73 | 66.5% faster |
| nested merged style array | 278.54 | 122.06 | 56.2% faster |
This is not a blanket claim that every React Native screen becomes 50%+ faster. It is targeted to the `flattenStyle` path when composed components pass nested style arrays.
## Validation
- `yarn test` in benchmark app repo
- `yarn lint` in benchmark app repo
- `yarn workspace style-flatten-benchmark-app tsc --noEmit`
- `yarn bench:compare`
- `yarn test packages/react-native/Libraries/StyleSheet/__tests__/flattenStyle-test.js --runInBand` in the React Native checkout
- iOS simulator build/install for the benchmark app
Draft while broader upstream validation is gathered.
## Affected areas
`flattenStyle` is not only the public `StyleSheet.flatten` helper. Core React Native components call it when they need to read or normalize style props before passing work further down:
- `Text` uses it in `packages/react-native/Libraries/Text/Text.js:188` before normalizing text style values such as numeric `fontWeight` and text selection-related props.
- `Image` uses it on both platforms: `Image.ios.js:141` reads `objectFit`, `resizeMode`, and `tintColor`; `Image.android.js:310` reads `objectFit` and `resizeMode` before building native props.
- `ImageBackground` uses it in `packages/react-native/Libraries/Image/ImageBackground.js:72` before splitting size-related style values between the wrapper view and inner image.
- `TextInput` uses it in `packages/react-native/Libraries/Components/TextInput/TextInput.js:655` while preserving the original style when possible, but still flattening to normalize text style overrides.
- `TouchableOpacity` uses it in `packages/react-native/Libraries/Components/Touchable/TouchableOpacity.js:260` and `:364` to read opacity from `style` when setting and updating its animated opacity.
- `ScrollView` uses it in `packages/react-native/Libraries/Components/ScrollView/ScrollView.js:1663` for development-time layout warnings and in `:1850` when splitting outer and inner layout props around refresh controls.
- `Animated.ScrollView` uses the same split path in `packages/react-native/Libraries/Animated/components/AnimatedScrollView.js:94`.
- Animated props use it in `packages/react-native/Libraries/Animated/nodes/AnimatedProps.js:62` and `:157`, and the newer memo hook uses it in `packages/react-native/src/private/animated/createAnimatedPropsMemoHook.js:125`, so nested style arrays also matter for animated style props.
- Fabric public instances use it in `packages/react-native/Libraries/ReactNative/ReactFabricPublicInstance/ReactNativeAttributePayload.js:186` and `:195` before diffing array style props.
- Developer tooling also goes through it: the element inspector uses it in `ElementProperties.js:43` and `ElementBox.js:34`, and React DevTools receives it as the React Native style resolver from `setUpReactDevTools.js:74`.
That is why the benchmark focuses on nested style arrays instead of claiming every render gets faster. The change helps when those call sites receive styles composed like `style={[base, condition && extra, [override]]}`.
## Changelog:
[GENERAL] [CHANGED] - Make `flattenStyle` avoid extra intermediate objects when flattening nested style arrays.
Pull Request resolved: #57203
Test Plan:
- Ran the focused React Native unit test: `yarn test packages/react-native/Libraries/StyleSheet/__tests__/flattenStyle-test.js --runInBand`.
- Ran the external benchmark app checks: `yarn test`, `yarn lint`, and `yarn workspace style-flatten-benchmark-app tsc --noEmit`.
- Ran `yarn bench:compare` in the benchmark app repo. The latest saved result compares React Native `main` at `066c0d8bd8` with this branch at `81b5bc26b6` and shows the nested style-array cases improving from `294.79 ms` to `98.73 ms` and from `278.54 ms` to `122.06 ms`.
- Built and installed the benchmark app on iOS simulators to compare a `main` build and this branch side by side.
Reviewed By: javache
Differential Revision: D108616011
Pulled By: Abbondanzo
fbshipit-source-id: f1540a274b6919c43d137d6587ea4d11be2587961 parent 9d54391 commit d26f7b3
3 files changed
Lines changed: 131 additions & 13 deletions
File tree
- packages/react-native/Libraries/StyleSheet
- __tests__
Lines changed: 72 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
Lines changed: 31 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
94 | 94 | | |
95 | 95 | | |
96 | 96 | | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
97 | 128 | | |
98 | 129 | | |
99 | 130 | | |
| |||
Lines changed: 28 additions & 13 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
20 | 20 | | |
21 | 21 | | |
22 | 22 | | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
23 | 48 | | |
24 | 49 | | |
25 | 50 | | |
| |||
36 | 61 | | |
37 | 62 | | |
38 | 63 | | |
39 | | - | |
40 | | - | |
41 | | - | |
42 | | - | |
43 | | - | |
44 | | - | |
45 | | - | |
46 | | - | |
47 | | - | |
48 | | - | |
49 | | - | |
50 | | - | |
51 | | - | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
52 | 67 | | |
53 | 68 | | |
54 | 69 | | |
| |||
0 commit comments