Skip to content

Commit

Permalink
Move string ref coercion to JSX runtime (#28473)
Browse files Browse the repository at this point in the history
Based on:

- #28464

---

This moves the entire string ref implementation out Fiber and into the
JSX runtime. The string is converted to a callback ref during element
creation. This is a subtle change in behavior, because it will have
already been converted to a callback ref if you access element.prop.ref
or element.ref. But this is only for Meta, because string refs are
disabled entirely in open source. And if it leads to an issue in
practice, the solution is to switch to a different ref type, which Meta
is going to do regardless.

DiffTrain build for [e3ebcd5](e3ebcd5)
  • Loading branch information
acdlite committed Apr 5, 2024
1 parent 769be69 commit e6ea24b
Show file tree
Hide file tree
Showing 27 changed files with 2,022 additions and 1,972 deletions.
287 changes: 279 additions & 8 deletions compiled/facebook-www/JSXDEVRuntime-dev.classic.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,8 +112,11 @@ if (__DEV__) {
enableRefAsProp = dynamicFeatureFlags.enableRefAsProp,
disableDefaultPropsExceptForClasses =
dynamicFeatureFlags.disableDefaultPropsExceptForClasses; // On WWW, false is used for a new modern build.
// because JSX is an extremely hot path.

function getWrappedName(outerType, innerType, wrapperName) {
var disableStringRefs = false;

function getWrappedName$1(outerType, innerType, wrapperName) {
var displayName = outerType.displayName;

if (displayName) {
Expand All @@ -126,7 +129,7 @@ if (__DEV__) {
: wrapperName;
} // Keep in sync with react-reconciler/getComponentNameFromFiber

function getContextName(type) {
function getContextName$1(type) {
return type.displayName || "Context";
}

Expand Down Expand Up @@ -193,28 +196,28 @@ if (__DEV__) {
return null;
} else {
var provider = type;
return getContextName(provider._context) + ".Provider";
return getContextName$1(provider._context) + ".Provider";
}

case REACT_CONTEXT_TYPE:
var context = type;

if (enableRenderableContext) {
return getContextName(context) + ".Provider";
return getContextName$1(context) + ".Provider";
} else {
return getContextName(context) + ".Consumer";
return getContextName$1(context) + ".Consumer";
}

case REACT_CONSUMER_TYPE:
if (enableRenderableContext) {
var consumer = type;
return getContextName(consumer._context) + ".Consumer";
return getContextName$1(consumer._context) + ".Consumer";
} else {
return null;
}

case REACT_FORWARD_REF_TYPE:
return getWrappedName(type, type.render, "ForwardRef");
return getWrappedName$1(type, type.render, "ForwardRef");

case REACT_MEMO_TYPE:
var outerName = type.displayName || null;
Expand Down Expand Up @@ -323,6 +326,20 @@ if (__DEV__) {
}
}
}
function checkPropStringCoercion(value, propName) {
{
if (willCoercionThrow(value)) {
error(
"The provided `%s` prop is an unsupported type %s." +
" This value must be coerced to a string before using it here.",
propName,
typeName(value)
);

return testStringCoercion(value); // throw (to help callers find troubleshooting comments)
}
}
}

var REACT_CLIENT_REFERENCE$1 = Symbol.for("react.client.reference");
function isValidElementType(type) {
Expand Down Expand Up @@ -802,6 +819,158 @@ if (__DEV__) {
return "";
}

var FunctionComponent = 0;
var ClassComponent = 1;
var HostRoot = 3; // Root of a host tree. Could be nested inside another node.

var HostPortal = 4; // A subtree. Could be an entry point to a different renderer.

var HostComponent = 5;
var HostText = 6;
var Fragment = 7;
var Mode = 8;
var ContextConsumer = 9;
var ContextProvider = 10;
var ForwardRef = 11;
var Profiler = 12;
var SuspenseComponent = 13;
var MemoComponent = 14;
var SimpleMemoComponent = 15;
var LazyComponent = 16;
var IncompleteClassComponent = 17;
var DehydratedFragment = 18;
var SuspenseListComponent = 19;
var ScopeComponent = 21;
var OffscreenComponent = 22;
var LegacyHiddenComponent = 23;
var CacheComponent = 24;
var TracingMarkerComponent = 25;
var HostHoistable = 26;
var HostSingleton = 27;
var IncompleteFunctionComponent = 28;

function getWrappedName(outerType, innerType, wrapperName) {
var functionName = innerType.displayName || innerType.name || "";
return (
outerType.displayName ||
(functionName !== ""
? wrapperName + "(" + functionName + ")"
: wrapperName)
);
} // Keep in sync with shared/getComponentNameFromType

function getContextName(type) {
return type.displayName || "Context";
}

function getComponentNameFromFiber(fiber) {
var tag = fiber.tag,
type = fiber.type;

switch (tag) {
case CacheComponent:
return "Cache";

case ContextConsumer:
if (enableRenderableContext) {
var consumer = type;
return getContextName(consumer._context) + ".Consumer";
} else {
var context = type;
return getContextName(context) + ".Consumer";
}

case ContextProvider:
if (enableRenderableContext) {
var _context = type;
return getContextName(_context) + ".Provider";
} else {
var provider = type;
return getContextName(provider._context) + ".Provider";
}

case DehydratedFragment:
return "DehydratedFragment";

case ForwardRef:
return getWrappedName(type, type.render, "ForwardRef");

case Fragment:
return "Fragment";

case HostHoistable:
case HostSingleton:
case HostComponent:
// Host component type is the display name (e.g. "div", "View")
return type;

case HostPortal:
return "Portal";

case HostRoot:
return "Root";

case HostText:
return "Text";

case LazyComponent:
// Name comes from the type in this case; we don't have a tag.
return getComponentNameFromType(type);

case Mode:
if (type === REACT_STRICT_MODE_TYPE) {
// Don't be less specific than shared/getComponentNameFromType
return "StrictMode";
}

return "Mode";

case OffscreenComponent:
return "Offscreen";

case Profiler:
return "Profiler";

case ScopeComponent:
return "Scope";

case SuspenseComponent:
return "Suspense";

case SuspenseListComponent:
return "SuspenseList";

case TracingMarkerComponent:
return "TracingMarker";
// The display name for these tags come from the user-provided type:

case IncompleteClassComponent:
case IncompleteFunctionComponent:

// Fallthrough

case ClassComponent:
case FunctionComponent:
case MemoComponent:
case SimpleMemoComponent:
if (typeof type === "function") {
return type.displayName || type.name || null;
}

if (typeof type === "string") {
return type;
}

break;

case LegacyHiddenComponent: {
return "LegacyHidden";
}
}

return null;
}

var ReactCurrentOwner = ReactSharedInternals.ReactCurrentOwner;
var ReactDebugCurrentFrame = ReactSharedInternals.ReactDebugCurrentFrame;
var REACT_CLIENT_REFERENCE = Symbol.for("react.client.reference");
Expand Down Expand Up @@ -1217,6 +1386,10 @@ if (__DEV__) {
if (hasValidRef(config)) {
if (!enableRefAsProp) {
ref = config.ref;

{
ref = coerceStringRef(ref, ReactCurrentOwner.current, type);
}
}

{
Expand All @@ -1230,7 +1403,15 @@ if (__DEV__) {
propName !== "key" &&
(enableRefAsProp || propName !== "ref")
) {
props[propName] = config[propName];
if (enableRefAsProp && !disableStringRefs && propName === "ref") {
props.ref = coerceStringRef(
config[propName],
ReactCurrentOwner.current,
type
);
} else {
props[propName] = config[propName];
}
}
}

Expand Down Expand Up @@ -1488,6 +1669,96 @@ if (__DEV__) {
}
}

function coerceStringRef(mixedRef, owner, type) {
var stringRef;

if (typeof mixedRef === "string") {
stringRef = mixedRef;
} else {
if (typeof mixedRef === "number" || typeof mixedRef === "boolean") {
{
checkPropStringCoercion(mixedRef, "ref");
}

stringRef = "" + mixedRef;
} else {
return mixedRef;
}
}

return stringRefAsCallbackRef.bind(null, stringRef, type, owner);
}

function stringRefAsCallbackRef(stringRef, type, owner, value) {
if (!owner) {
throw new Error(
"Element ref was specified as a string (" +
stringRef +
") but no owner was set. This could happen for one of" +
" the following reasons:\n" +
"1. You may be adding a ref to a function component\n" +
"2. You may be adding a ref to a component that was not created inside a component's render method\n" +
"3. You have multiple copies of React loaded\n" +
"See https://react.dev/link/refs-must-have-owner for more information."
);
}

if (owner.tag !== ClassComponent) {
throw new Error(
"Function components cannot have string refs. " +
"We recommend using useRef() instead. " +
"Learn more about using refs safely here: " +
"https://react.dev/link/strict-mode-string-ref"
);
}

{
if (
// Will already warn with "Function components cannot be given refs"
!(typeof type === "function" && !isReactClass(type))
) {
var componentName = getComponentNameFromFiber(owner) || "Component";

if (!didWarnAboutStringRefs[componentName]) {
error(
'Component "%s" contains the string ref "%s". Support for string refs ' +
"will be removed in a future major release. We recommend using " +
"useRef() or createRef() instead. " +
"Learn more about using refs safely here: " +
"https://react.dev/link/strict-mode-string-ref",
componentName,
stringRef
);

didWarnAboutStringRefs[componentName] = true;
}
}
}

var inst = owner.stateNode;

if (!inst) {
throw new Error(
"Missing owner for string ref " +
stringRef +
". This error is likely caused by a " +
"bug in React. Please file an issue."
);
}

var refs = inst.refs;

if (value === null) {
delete refs[stringRef];
} else {
refs[stringRef] = value;
}
}

function isReactClass(type) {
return type.prototype && type.prototype.isReactComponent;
}

var jsxDEV = jsxDEV$1;

exports.Fragment = REACT_FRAGMENT_TYPE;
Expand Down

0 comments on commit e6ea24b

Please sign in to comment.