Pass ref as normal prop (#28348)

Depends on:

- #28317
- #28320

---

Changes the behavior of the JSX runtime to pass through `ref` as a
normal prop, rather than plucking it from the props object and storing
on the element.

This is a breaking change since it changes the type of the receiving
component. However, most code is unaffected since it's unlikely that a
component would have attempted to access a `ref` prop, since it was not
possible to get a reference to one.

`forwardRef` _will_ still pluck `ref` from the props object, though,
since it's extremely common for users to spread the props object onto
the inner component and pass `ref` as a differently named prop. This is
for maximum compatibility with existing code — the real impact of this
change is that `forwardRef` is no longer required.

Currently, refs are resolved during child reconciliation and stored on
the fiber. As a result of this change, we can move ref resolution to
happen only much later, and only for components that actually use them.
Then we can remove the `ref` field from the Fiber type. I have not yet
done that in this step, though.

DiffTrain build for commit https://github.com/facebook/react/commit/fa2f82addc7c817892c482792f56a35277e8b75a.
This commit is contained in:
acdlite
2024-02-20 19:22:36 +00:00
parent 5a8f5c841b
commit c25dcdbe80
11 changed files with 232 additions and 138 deletions
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<bdf0df21b100941fad1618d2f0f4ea03>>
* @generated SignedSource<<1d3d484449a45d9d48c574d191f78a9b>>
*/
"use strict";
@@ -5040,7 +5040,12 @@ if (__DEV__) {
}
function coerceRef(returnFiber, current, element) {
var mixedRef = element.ref;
var mixedRef;
{
// Old behavior.
mixedRef = element.ref;
}
if (
mixedRef !== null &&
@@ -12692,7 +12697,12 @@ if (__DEV__) {
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.
var render = Component.render;
var ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent
var ref = workInProgress.ref;
var propsWithoutRef;
{
propsWithoutRef = nextProps;
} // The rest is a fork of updateFunctionComponent
var nextChildren;
prepareToReadContext(workInProgress, renderLanes);
@@ -12704,7 +12714,7 @@ if (__DEV__) {
current,
workInProgress,
render,
nextProps,
propsWithoutRef,
ref,
renderLanes
);
@@ -25686,7 +25696,7 @@ if (__DEV__) {
return root;
}
var ReactVersion = "18.3.0-canary-7b196be09-20240220";
var ReactVersion = "18.3.0-canary-fa2f82add-20240220";
// Might add PROFILE later.
@@ -9176,7 +9176,7 @@ var devToolsConfig$jscomp$inline_1014 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "18.3.0-canary-7b196be09-20240220",
version: "18.3.0-canary-fa2f82add-20240220",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1195 = {
@@ -9207,7 +9207,7 @@ var internals$jscomp$inline_1195 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-canary-7b196be09-20240220"
reconcilerVersion: "18.3.0-canary-fa2f82add-20240220"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1196 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -9604,7 +9604,7 @@ var devToolsConfig$jscomp$inline_1056 = {
throw Error("TestRenderer does not support findFiberByHostInstance()");
},
bundleType: 0,
version: "18.3.0-canary-7b196be09-20240220",
version: "18.3.0-canary-fa2f82add-20240220",
rendererPackageName: "react-test-renderer"
};
var internals$jscomp$inline_1236 = {
@@ -9635,7 +9635,7 @@ var internals$jscomp$inline_1236 = {
scheduleRoot: null,
setRefreshHandler: null,
getCurrentFiber: null,
reconcilerVersion: "18.3.0-canary-7b196be09-20240220"
reconcilerVersion: "18.3.0-canary-fa2f82add-20240220"
};
if ("undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__) {
var hook$jscomp$inline_1237 = __REACT_DEVTOOLS_GLOBAL_HOOK__;
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<9d1803d74418a7ac44bc89ae8cd0f170>>
* @generated SignedSource<<20a889fc4c8eaee85875c7091bc5ea65>>
*/
"use strict";
@@ -643,25 +643,27 @@ if (__DEV__) {
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
{
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error(
"%s: `ref` is not a prop. Trying to access it will result " +
"in `undefined` being returned. If you need to access the same " +
"value within the child component, you should pass it as a different " +
"prop. (https://reactjs.org/link/special-props)",
displayName
);
}
};
error(
"%s: `ref` is not a prop. Trying to access it will result " +
"in `undefined` being returned. If you need to access the same " +
"value within the child component, you should pass it as a different " +
"prop. (https://reactjs.org/link/special-props)",
displayName
);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
}
/**
@@ -685,18 +687,30 @@ if (__DEV__) {
* @internal
*/
function ReactElement(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
function ReactElement(type, key, _ref, self, source, owner, props) {
var ref;
{
ref = _ref;
}
var element;
{
// In prod, `ref` is a regular property. It will be removed in a
// future release.
element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
}
{
// The validation flag is currently mutative. We put it on
@@ -882,14 +896,17 @@ if (__DEV__) {
}
if (hasValidRef(config)) {
ref = config.ref;
{
ref = config.ref;
}
warnIfStringRefCannotBeAutoConverted(config, self);
} // Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) && // Skip over reserved prop names
propName !== "key" && // TODO: `ref` will no longer be reserved in the next major
propName !== "key" &&
propName !== "ref"
) {
props[propName] = config[propName];
@@ -1130,6 +1147,7 @@ if (__DEV__) {
*/
function validateFragmentProps(fragment) {
// TODO: Move this to render phase instead of at element creation.
{
var keys = Object.keys(fragment.props);
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<8af9696c59d9d4cfa4cc835b5a54e94e>>
* @generated SignedSource<<74f1366b0384318710be72f745716987>>
*/
"use strict";
@@ -643,25 +643,27 @@ if (__DEV__) {
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
{
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error(
"%s: `ref` is not a prop. Trying to access it will result " +
"in `undefined` being returned. If you need to access the same " +
"value within the child component, you should pass it as a different " +
"prop. (https://reactjs.org/link/special-props)",
displayName
);
}
};
error(
"%s: `ref` is not a prop. Trying to access it will result " +
"in `undefined` being returned. If you need to access the same " +
"value within the child component, you should pass it as a different " +
"prop. (https://reactjs.org/link/special-props)",
displayName
);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
}
/**
@@ -685,18 +687,30 @@ if (__DEV__) {
* @internal
*/
function ReactElement(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
function ReactElement(type, key, _ref, self, source, owner, props) {
var ref;
{
ref = _ref;
}
var element;
{
// In prod, `ref` is a regular property. It will be removed in a
// future release.
element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
}
{
// The validation flag is currently mutative. We put it on
@@ -918,14 +932,17 @@ if (__DEV__) {
}
if (hasValidRef(config)) {
ref = config.ref;
{
ref = config.ref;
}
warnIfStringRefCannotBeAutoConverted(config, self);
} // Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) && // Skip over reserved prop names
propName !== "key" && // TODO: `ref` will no longer be reserved in the next major
propName !== "key" &&
propName !== "ref"
) {
props[propName] = config[propName];
@@ -1166,6 +1183,7 @@ if (__DEV__) {
*/
function validateFragmentProps(fragment) {
// TODO: Move this to render phase instead of at element creation.
{
var keys = Object.keys(fragment.props);
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<771f900d99c19be9a9ca908e40980fa4>>
* @generated SignedSource<<9e068e993226e000d2d2029d54ab83b7>>
*/
"use strict";
@@ -24,7 +24,7 @@ if (__DEV__) {
) {
__REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStart(new Error());
}
var ReactVersion = "18.3.0-canary-7b196be09-20240220";
var ReactVersion = "18.3.0-canary-fa2f82add-20240220";
// ATTENTION
// When adding new symbols to this file,
@@ -568,6 +568,9 @@ if (__DEV__) {
var enableRenderableContext = false;
var enableLegacyHidden = false;
var enableTransitionTracing = false;
// because JSX is an extremely hot path.
var enableRefAsProp = false; // Flow magic to verify the exports of this file match the original version.
function getWrappedName(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;
@@ -1023,25 +1026,27 @@ if (__DEV__) {
function defineRefPropWarningGetter(props, displayName) {
{
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
{
var warnAboutAccessingRef = function () {
if (!specialPropRefWarningShown) {
specialPropRefWarningShown = true;
error(
"%s: `ref` is not a prop. Trying to access it will result " +
"in `undefined` being returned. If you need to access the same " +
"value within the child component, you should pass it as a different " +
"prop. (https://reactjs.org/link/special-props)",
displayName
);
}
};
error(
"%s: `ref` is not a prop. Trying to access it will result " +
"in `undefined` being returned. If you need to access the same " +
"value within the child component, you should pass it as a different " +
"prop. (https://reactjs.org/link/special-props)",
displayName
);
}
};
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
warnAboutAccessingRef.isReactWarning = true;
Object.defineProperty(props, "ref", {
get: warnAboutAccessingRef,
configurable: true
});
}
}
}
/**
@@ -1065,18 +1070,30 @@ if (__DEV__) {
* @internal
*/
function ReactElement(type, key, ref, self, source, owner, props) {
var element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
function ReactElement(type, key, _ref, self, source, owner, props) {
var ref;
{
ref = _ref;
}
var element;
{
// In prod, `ref` is a regular property. It will be removed in a
// future release.
element = {
// This tag allows us to uniquely identify this as a React Element
$$typeof: REACT_ELEMENT_TYPE,
// Built-in properties that belong on the element
type: type,
key: key,
ref: ref,
props: props,
// Record the component responsible for creating this element.
_owner: owner
};
}
{
// The validation flag is currently mutative. We put it on
@@ -1298,14 +1315,17 @@ if (__DEV__) {
}
if (hasValidRef(config)) {
ref = config.ref;
{
ref = config.ref;
}
warnIfStringRefCannotBeAutoConverted(config, self);
} // Remaining properties are added to a new props object
for (propName in config) {
if (
hasOwnProperty.call(config, propName) && // Skip over reserved prop names
propName !== "key" && // TODO: `ref` will no longer be reserved in the next major
propName !== "key" &&
propName !== "ref"
) {
props[propName] = config[propName];
@@ -1435,7 +1455,9 @@ if (__DEV__) {
if (config != null) {
if (hasValidRef(config)) {
ref = config.ref;
{
ref = config.ref;
}
{
warnIfStringRefCannotBeAutoConverted(config, config.__self);
@@ -1453,13 +1475,11 @@ if (__DEV__) {
for (propName in config) {
if (
hasOwnProperty.call(config, propName) && // Skip over reserved prop names
propName !== "key" && // TODO: `ref` will no longer be reserved in the next major
propName !== "ref" && // ...and maybe these, too, though we currently rely on them for
// warnings and debug information in dev. Need to decide if we're OK
// with dropping them. In the jsx() runtime it's not an issue because
// the data gets passed as separate arguments instead of props, but
// it would be nice to stop relying on them entirely so we can drop
// them from the internal Fiber field.
propName !== "key" &&
propName !== "ref" && // Even though we don't use these anymore in the runtime, we don't want
// them to appear as props, so in createElement we filter them out.
// We don't have to do this in the jsx() runtime because the jsx()
// transform never passed these as props; it used separate arguments.
propName !== "__self" &&
propName !== "__source"
) {
@@ -1581,7 +1601,8 @@ if (__DEV__) {
function cloneAndReplaceKey(oldElement, newKey) {
return ReactElement(
oldElement.type,
newKey,
newKey, // When enableRefAsProp is on, this argument is ignored. This check only
// exists to avoid the `ref` access warning.
oldElement.ref,
undefined,
undefined,
@@ -1614,8 +1635,11 @@ if (__DEV__) {
if (config != null) {
if (hasValidRef(config)) {
// Silently steal the ref from the parent.
ref = config.ref;
{
// Silently steal the ref from the parent.
ref = config.ref;
}
owner = ReactCurrentOwner.current;
}
@@ -1636,7 +1660,7 @@ if (__DEV__) {
for (propName in config) {
if (
hasOwnProperty.call(config, propName) && // Skip over reserved prop names
propName !== "key" && // TODO: `ref` will no longer be reserved in the next major
propName !== "key" &&
propName !== "ref" && // ...and maybe these, too, though we currently rely on them for
// warnings and debug information in dev. Need to decide if we're OK
// with dropping them. In the jsx() runtime it's not an issue because
@@ -1644,7 +1668,10 @@ if (__DEV__) {
// it would be nice to stop relying on them entirely so we can drop
// them from the internal Fiber field.
propName !== "__self" &&
propName !== "__source"
propName !== "__source" && // Undefined `ref` is ignored by cloneElement. We treat it the same as
// if the property were missing. This is mostly for
// backwards compatibility.
!enableRefAsProp
) {
if (config[propName] === undefined && defaultProps !== undefined) {
// Resolve default props
@@ -1886,6 +1913,7 @@ if (__DEV__) {
*/
function validateFragmentProps(fragment) {
// TODO: Move this to render phase instead of at element creation.
{
var keys = Object.keys(fragment.props);
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<41cda10cf320dab6684351ebb1b6c769>>
* @generated SignedSource<<37f4ed7242b99027f4654904bb633f36>>
*/
"use strict";
@@ -92,12 +92,12 @@ var isArrayImpl = Array.isArray,
},
hasOwnProperty = Object.prototype.hasOwnProperty,
ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
function ReactElement(type, key, ref, self, source, owner, props) {
function ReactElement(type, key, _ref, self, source, owner, props) {
return {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
ref: _ref,
props: props,
_owner: owner
};
@@ -600,4 +600,4 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-canary-7b196be09-20240220";
exports.version = "18.3.0-canary-fa2f82add-20240220";
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<2014363963cd257db2e2ba2e30cd8535>>
* @generated SignedSource<<04ca2715fce5363a86317b200bb764c0>>
*/
"use strict";
@@ -95,12 +95,12 @@ var isArrayImpl = Array.isArray,
},
hasOwnProperty = Object.prototype.hasOwnProperty,
ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
function ReactElement(type, key, ref, self, source, owner, props) {
function ReactElement(type, key, _ref, self, source, owner, props) {
return {
$$typeof: REACT_ELEMENT_TYPE,
type: type,
key: key,
ref: ref,
ref: _ref,
props: props,
_owner: owner
};
@@ -596,7 +596,7 @@ exports.useSyncExternalStore = function (
exports.useTransition = function () {
return ReactCurrentDispatcher.current.useTransition();
};
exports.version = "18.3.0-canary-7b196be09-20240220";
exports.version = "18.3.0-canary-fa2f82add-20240220";
"undefined" !== typeof __REACT_DEVTOOLS_GLOBAL_HOOK__ &&
"function" ===
typeof __REACT_DEVTOOLS_GLOBAL_HOOK__.registerInternalModuleStop &&
@@ -1 +1 @@
7b196be09167f8fe9091168dab1769a552263065
fa2f82addc7c817892c482792f56a35277e8b75a
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<062290072ee67d2ab7e1ce078ec87315>>
* @generated SignedSource<<383f07630be8104f7153897f592f9079>>
*/
"use strict";
@@ -8679,7 +8679,12 @@ to return true:wantsResponderID| |
}
function coerceRef(returnFiber, current, element) {
var mixedRef = element.ref;
var mixedRef;
{
// Old behavior.
mixedRef = element.ref;
}
if (
mixedRef !== null &&
@@ -15709,7 +15714,12 @@ to return true:wantsResponderID| |
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.
var render = Component.render;
var ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent
var ref = workInProgress.ref;
var propsWithoutRef;
{
propsWithoutRef = nextProps;
} // The rest is a fork of updateFunctionComponent
var nextChildren;
prepareToReadContext(workInProgress, renderLanes);
@@ -15725,7 +15735,7 @@ to return true:wantsResponderID| |
current,
workInProgress,
render,
nextProps,
propsWithoutRef,
ref,
renderLanes
);
@@ -27725,7 +27735,7 @@ to return true:wantsResponderID| |
return root;
}
var ReactVersion = "18.3.0-canary-df883c3c";
var ReactVersion = "18.3.0-canary-232f2dc2";
function createPortal$1(
children,
@@ -7,7 +7,7 @@
* @noflow
* @nolint
* @preventMunge
* @generated SignedSource<<7f468c0b121f290e67f1fba4e2a73c8e>>
* @generated SignedSource<<7dd934733eaf5f0f6996f44b50a9a9b4>>
*/
"use strict";
@@ -8951,7 +8951,12 @@ to return true:wantsResponderID| |
}
function coerceRef(returnFiber, current, element) {
var mixedRef = element.ref;
var mixedRef;
{
// Old behavior.
mixedRef = element.ref;
}
if (
mixedRef !== null &&
@@ -15981,7 +15986,12 @@ to return true:wantsResponderID| |
// hasn't yet mounted. This happens after the first render suspends.
// We'll need to figure out if this is fine or can cause issues.
var render = Component.render;
var ref = workInProgress.ref; // The rest is a fork of updateFunctionComponent
var ref = workInProgress.ref;
var propsWithoutRef;
{
propsWithoutRef = nextProps;
} // The rest is a fork of updateFunctionComponent
var nextChildren;
prepareToReadContext(workInProgress, renderLanes);
@@ -15997,7 +16007,7 @@ to return true:wantsResponderID| |
current,
workInProgress,
render,
nextProps,
propsWithoutRef,
ref,
renderLanes
);
@@ -28166,7 +28176,7 @@ to return true:wantsResponderID| |
return root;
}
var ReactVersion = "18.3.0-canary-9ee917a0";
var ReactVersion = "18.3.0-canary-7ee8e435";
function createPortal$1(
children,