mirror of
https://github.com/facebook/react-native.git
synced 2025-11-01 09:14:26 +00:00
Remove any props set by default on View (#51225)
Summary: Pull Request resolved: https://github.com/facebook/react-native/pull/51225 We process a number of props by default in View, so we can provide some amount of compatibility with web API's. The way we then pass these to React means we end up setting a number of props with 'undefined' values. These props need to be diffed, sent to native (so string keys copied via JSI) and serialized to folly::dynamic (on Android) which is just wasteful. Instead we can mutate the destructured props object and update/insert keys only as necessary to reduce the props payload size. Changelog: [General][Breaking] View no longer sets any default accessibility props, which should not result in visible changes in behaviour but may affect snapshot tests. Reviewed By: yungsters Differential Revision: D74472767 fbshipit-source-id: 462a4495c0672d4bf1752a532acff49b14598e8e
This commit is contained in:
committed by
Facebook GitHub Bot
parent
ee9bd851ac
commit
039a333df5
+174
-78
@@ -10,12 +10,18 @@
|
||||
|
||||
import type {ViewProps} from './ViewPropTypes';
|
||||
|
||||
import * as ReactNativeFeatureFlags from '../../../src/private/featureflags/ReactNativeFeatureFlags';
|
||||
import TextAncestor from '../../Text/TextAncestor';
|
||||
import ViewNativeComponent from './ViewNativeComponent';
|
||||
import * as React from 'react';
|
||||
|
||||
export type Props = ViewProps;
|
||||
|
||||
type PropsWithRef = $ReadOnly<{
|
||||
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
|
||||
...ViewProps,
|
||||
}>;
|
||||
|
||||
/**
|
||||
* The most fundamental component for building a UI, View is a container that
|
||||
* supports layout with flexbox, style, some touch handling, and accessibility
|
||||
@@ -23,93 +29,183 @@ export type Props = ViewProps;
|
||||
*
|
||||
* @see https://reactnative.dev/docs/view
|
||||
*/
|
||||
function View({
|
||||
accessibilityElementsHidden,
|
||||
accessibilityLabel,
|
||||
accessibilityLabelledBy,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityState,
|
||||
accessibilityValue,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-hidden': ariaHidden,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
'aria-live': ariaLive,
|
||||
'aria-selected': ariaSelected,
|
||||
'aria-valuemax': ariaValueMax,
|
||||
'aria-valuemin': ariaValueMin,
|
||||
'aria-valuenow': ariaValueNow,
|
||||
'aria-valuetext': ariaValueText,
|
||||
focusable,
|
||||
id,
|
||||
importantForAccessibility,
|
||||
nativeID,
|
||||
tabIndex,
|
||||
...otherProps
|
||||
}: $ReadOnly<{
|
||||
ref?: React.RefSetter<React.ElementRef<typeof ViewNativeComponent>>,
|
||||
...ViewProps,
|
||||
}>): React.Node {
|
||||
function View(props: PropsWithRef): React.Node {
|
||||
const hasTextAncestor = React.use(TextAncestor);
|
||||
const _accessibilityLabelledBy =
|
||||
ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;
|
||||
|
||||
const _accessibilityState =
|
||||
accessibilityState != null ||
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
? {
|
||||
busy: ariaBusy ?? accessibilityState?.busy,
|
||||
checked: ariaChecked ?? accessibilityState?.checked,
|
||||
disabled: ariaDisabled ?? accessibilityState?.disabled,
|
||||
expanded: ariaExpanded ?? accessibilityState?.expanded,
|
||||
selected: ariaSelected ?? accessibilityState?.selected,
|
||||
}
|
||||
: undefined;
|
||||
let actualView;
|
||||
if (ReactNativeFeatureFlags.reduceDefaultPropsInView()) {
|
||||
const {
|
||||
accessibilityState,
|
||||
accessibilityValue,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-hidden': ariaHidden,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
'aria-live': ariaLive,
|
||||
'aria-selected': ariaSelected,
|
||||
'aria-valuemax': ariaValueMax,
|
||||
'aria-valuemin': ariaValueMin,
|
||||
'aria-valuenow': ariaValueNow,
|
||||
'aria-valuetext': ariaValueText,
|
||||
id,
|
||||
tabIndex,
|
||||
...otherProps
|
||||
} = props;
|
||||
|
||||
const _accessibilityValue =
|
||||
accessibilityValue != null ||
|
||||
ariaValueMax != null ||
|
||||
ariaValueMin != null ||
|
||||
ariaValueNow != null ||
|
||||
ariaValueText != null
|
||||
? {
|
||||
max: ariaValueMax ?? accessibilityValue?.max,
|
||||
min: ariaValueMin ?? accessibilityValue?.min,
|
||||
now: ariaValueNow ?? accessibilityValue?.now,
|
||||
text: ariaValueText ?? accessibilityValue?.text,
|
||||
}
|
||||
: undefined;
|
||||
// Since we destructured props, we can now treat it as mutable
|
||||
const processedProps = otherProps as {...PropsWithRef};
|
||||
|
||||
const actualView = (
|
||||
<ViewNativeComponent
|
||||
{...otherProps}
|
||||
accessibilityLiveRegion={
|
||||
ariaLive === 'off' ? 'none' : ariaLive ?? accessibilityLiveRegion
|
||||
const parsedAriaLabelledBy = ariaLabelledBy?.split(/\s*,\s*/g);
|
||||
if (parsedAriaLabelledBy !== undefined) {
|
||||
processedProps.accessibilityLabelledBy = parsedAriaLabelledBy;
|
||||
}
|
||||
|
||||
if (ariaLabel !== undefined) {
|
||||
processedProps.accessibilityLabel = ariaLabel;
|
||||
}
|
||||
|
||||
if (ariaLive !== undefined) {
|
||||
processedProps.accessibilityLiveRegion =
|
||||
ariaLive === 'off' ? 'none' : ariaLive;
|
||||
}
|
||||
|
||||
if (ariaHidden !== undefined) {
|
||||
processedProps.accessibilityElementsHidden = ariaHidden;
|
||||
if (ariaHidden === true) {
|
||||
processedProps.importantForAccessibility = 'no-hide-descendants';
|
||||
}
|
||||
accessibilityLabel={ariaLabel ?? accessibilityLabel}
|
||||
focusable={tabIndex !== undefined ? !tabIndex : focusable}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessibilityElementsHidden={ariaHidden ?? accessibilityElementsHidden}
|
||||
accessibilityLabelledBy={_accessibilityLabelledBy}
|
||||
accessibilityValue={_accessibilityValue}
|
||||
importantForAccessibility={
|
||||
ariaHidden === true ? 'no-hide-descendants' : importantForAccessibility
|
||||
}
|
||||
nativeID={id ?? nativeID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (id !== undefined) {
|
||||
processedProps.nativeID = id;
|
||||
}
|
||||
|
||||
if (tabIndex !== undefined) {
|
||||
processedProps.focusable = !tabIndex;
|
||||
}
|
||||
|
||||
if (
|
||||
accessibilityState != null ||
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
) {
|
||||
processedProps.accessibilityState = {
|
||||
busy: ariaBusy ?? accessibilityState?.busy,
|
||||
checked: ariaChecked ?? accessibilityState?.checked,
|
||||
disabled: ariaDisabled ?? accessibilityState?.disabled,
|
||||
expanded: ariaExpanded ?? accessibilityState?.expanded,
|
||||
selected: ariaSelected ?? accessibilityState?.selected,
|
||||
};
|
||||
}
|
||||
|
||||
if (
|
||||
accessibilityValue != null ||
|
||||
ariaValueMax != null ||
|
||||
ariaValueMin != null ||
|
||||
ariaValueNow != null ||
|
||||
ariaValueText != null
|
||||
) {
|
||||
processedProps.accessibilityValue = {
|
||||
max: ariaValueMax ?? accessibilityValue?.max,
|
||||
min: ariaValueMin ?? accessibilityValue?.min,
|
||||
now: ariaValueNow ?? accessibilityValue?.now,
|
||||
text: ariaValueText ?? accessibilityValue?.text,
|
||||
};
|
||||
}
|
||||
|
||||
actualView = <ViewNativeComponent {...processedProps} />;
|
||||
} else {
|
||||
const {
|
||||
accessibilityElementsHidden,
|
||||
accessibilityLabel,
|
||||
accessibilityLabelledBy,
|
||||
accessibilityLiveRegion,
|
||||
accessibilityState,
|
||||
accessibilityValue,
|
||||
'aria-busy': ariaBusy,
|
||||
'aria-checked': ariaChecked,
|
||||
'aria-disabled': ariaDisabled,
|
||||
'aria-expanded': ariaExpanded,
|
||||
'aria-hidden': ariaHidden,
|
||||
'aria-label': ariaLabel,
|
||||
'aria-labelledby': ariaLabelledBy,
|
||||
'aria-live': ariaLive,
|
||||
'aria-selected': ariaSelected,
|
||||
'aria-valuemax': ariaValueMax,
|
||||
'aria-valuemin': ariaValueMin,
|
||||
'aria-valuenow': ariaValueNow,
|
||||
'aria-valuetext': ariaValueText,
|
||||
focusable,
|
||||
id,
|
||||
importantForAccessibility,
|
||||
nativeID,
|
||||
tabIndex,
|
||||
...otherProps
|
||||
} = props;
|
||||
const _accessibilityLabelledBy =
|
||||
ariaLabelledBy?.split(/\s*,\s*/g) ?? accessibilityLabelledBy;
|
||||
|
||||
const _accessibilityState =
|
||||
accessibilityState != null ||
|
||||
ariaBusy != null ||
|
||||
ariaChecked != null ||
|
||||
ariaDisabled != null ||
|
||||
ariaExpanded != null ||
|
||||
ariaSelected != null
|
||||
? {
|
||||
busy: ariaBusy ?? accessibilityState?.busy,
|
||||
checked: ariaChecked ?? accessibilityState?.checked,
|
||||
disabled: ariaDisabled ?? accessibilityState?.disabled,
|
||||
expanded: ariaExpanded ?? accessibilityState?.expanded,
|
||||
selected: ariaSelected ?? accessibilityState?.selected,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
const _accessibilityValue =
|
||||
accessibilityValue != null ||
|
||||
ariaValueMax != null ||
|
||||
ariaValueMin != null ||
|
||||
ariaValueNow != null ||
|
||||
ariaValueText != null
|
||||
? {
|
||||
max: ariaValueMax ?? accessibilityValue?.max,
|
||||
min: ariaValueMin ?? accessibilityValue?.min,
|
||||
now: ariaValueNow ?? accessibilityValue?.now,
|
||||
text: ariaValueText ?? accessibilityValue?.text,
|
||||
}
|
||||
: undefined;
|
||||
|
||||
actualView = (
|
||||
<ViewNativeComponent
|
||||
{...otherProps}
|
||||
accessibilityLiveRegion={
|
||||
ariaLive === 'off' ? 'none' : ariaLive ?? accessibilityLiveRegion
|
||||
}
|
||||
accessibilityLabel={ariaLabel ?? accessibilityLabel}
|
||||
focusable={tabIndex !== undefined ? !tabIndex : focusable}
|
||||
accessibilityState={_accessibilityState}
|
||||
accessibilityElementsHidden={ariaHidden ?? accessibilityElementsHidden}
|
||||
accessibilityLabelledBy={_accessibilityLabelledBy}
|
||||
accessibilityValue={_accessibilityValue}
|
||||
importantForAccessibility={
|
||||
ariaHidden === true
|
||||
? 'no-hide-descendants'
|
||||
: importantForAccessibility
|
||||
}
|
||||
nativeID={id ?? nativeID}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
if (hasTextAncestor) {
|
||||
return <TextAncestor value={false}>{actualView}</TextAncestor>;
|
||||
}
|
||||
|
||||
return actualView;
|
||||
}
|
||||
|
||||
|
||||
@@ -653,6 +653,17 @@ const definitions: FeatureFlagDefinitions = {
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
reduceDefaultPropsInView: {
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
dateAdded: '2025-5-12',
|
||||
description:
|
||||
'Optimize how default (accessibility) props are processed in View to avoid unnecessary keys.',
|
||||
expectedReleaseValue: true,
|
||||
purpose: 'experimentation',
|
||||
},
|
||||
ossReleaseStage: 'none',
|
||||
},
|
||||
scheduleAnimatedCleanupInMicrotask: {
|
||||
defaultValue: true,
|
||||
metadata: {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
* This source code is licensed under the MIT license found in the
|
||||
* LICENSE file in the root directory of this source tree.
|
||||
*
|
||||
* @generated SignedSource<<84786200dbf1e0d95605152913b73423>>
|
||||
* @generated SignedSource<<2dc9ec2ea173f10586a67940720f1d37>>
|
||||
* @flow strict
|
||||
*/
|
||||
|
||||
@@ -37,6 +37,7 @@ export type ReactNativeFeatureFlagsJsOnly = $ReadOnly<{
|
||||
enableVirtualViewDoubleStateHidden: Getter<boolean>,
|
||||
fixVirtualizeListCollapseWindowSize: Getter<boolean>,
|
||||
isLayoutAnimationEnabled: Getter<boolean>,
|
||||
reduceDefaultPropsInView: Getter<boolean>,
|
||||
scheduleAnimatedCleanupInMicrotask: Getter<boolean>,
|
||||
shouldUseAnimatedObjectForTransform: Getter<boolean>,
|
||||
shouldUseRemoveClippedSubviewsAsDefaultOnIOS: Getter<boolean>,
|
||||
@@ -149,6 +150,11 @@ export const fixVirtualizeListCollapseWindowSize: Getter<boolean> = createJavaSc
|
||||
*/
|
||||
export const isLayoutAnimationEnabled: Getter<boolean> = createJavaScriptFlagGetter('isLayoutAnimationEnabled', true);
|
||||
|
||||
/**
|
||||
* Optimize how default (accessibility) props are processed in View to avoid unnecessary keys.
|
||||
*/
|
||||
export const reduceDefaultPropsInView: Getter<boolean> = createJavaScriptFlagGetter('reduceDefaultPropsInView', true);
|
||||
|
||||
/**
|
||||
* Changes the cleanup of `AnimatedProps` to occur in a microtask instead of synchronously during effect cleanup (for unmount) or subsequent mounts (for updates).
|
||||
*/
|
||||
|
||||
Reference in New Issue
Block a user