Skip to content

Commit 2f2484b

Browse files
committed
feat: dont remount children when unnecessary
1 parent 6b45e57 commit 2f2484b

1 file changed

Lines changed: 38 additions & 1 deletion

File tree

packages/react-native/React/Fabric/Mounting/ComponentViews/ScrollView/RCTScrollViewComponentView.mm

Lines changed: 38 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,41 @@ static UIScrollViewIndicatorStyle RCTUIScrollViewIndicatorStyleFromProps(const S
5858
}
5959
}
6060

61+
// Child clipping only depends on which descendants exist and their layout frames.
62+
// A mounting transaction that contains only prop updates with unchanged layout
63+
// metrics (e.g. opacity/transform commits from Reanimated, which fire every
64+
// animation frame) cannot change clipping, so re-clipping can be skipped.
65+
static BOOL RCTMountingTransactionAffectsClipping(const facebook::react::MountingTransaction &transaction)
66+
{
67+
for (const auto &mutation : transaction.getMutations()) {
68+
switch (mutation.type) {
69+
case facebook::react::ShadowViewMutation::Insert:
70+
case facebook::react::ShadowViewMutation::Remove:
71+
return YES;
72+
case facebook::react::ShadowViewMutation::Update: {
73+
const auto &oldChild = mutation.oldChildShadowView;
74+
const auto &newChild = mutation.newChildShadowView;
75+
if (oldChild.layoutMetrics != newChild.layoutMetrics) {
76+
return YES;
77+
}
78+
// A `removeClippedSubviews` toggle changes clipping without changing
79+
// layout, so it must also force a re-clip.
80+
if (oldChild.props && newChild.props) {
81+
const auto &oldProps = static_cast<const facebook::react::ViewProps &>(*oldChild.props);
82+
const auto &newProps = static_cast<const facebook::react::ViewProps &>(*newChild.props);
83+
if (oldProps.removeClippedSubviews != newProps.removeClippedSubviews) {
84+
return YES;
85+
}
86+
}
87+
break;
88+
}
89+
default:
90+
break;
91+
}
92+
}
93+
return NO;
94+
}
95+
6196
// Once Fabric implements proper NativeAnimationDriver, this should be removed.
6297
// This is just a workaround to allow animations based on onScroll event.
6398
// This is only used to animate sticky headers in ScrollViews, and only the contentOffset and tag is used.
@@ -292,7 +327,9 @@ - (void)mountingTransactionWillMount:(const facebook::react::MountingTransaction
292327
- (void)mountingTransactionDidMount:(const MountingTransaction &)transaction
293328
withSurfaceTelemetry:(const facebook::react::SurfaceTelemetry &)surfaceTelemetry
294329
{
295-
[self _remountChildren];
330+
if (RCTMountingTransactionAffectsClipping(transaction)) {
331+
[self _remountChildren];
332+
}
296333
[self _adjustForMaintainVisibleContentPosition];
297334
}
298335

0 commit comments

Comments
 (0)