@samitouri / QOS-React / commits / d285b3acba

Go back to shared refs instance object (#28911)

It turns out we already made refs writable in #25696, which has been in canary for over a year. The approach in that PR also has the benefit of being slightly more perf sensitive because it still uses a shared object until the fiber is mounted. So let's just go back to that.

Andrew Clark committed Apr 25, 2024 at 13:03 UTC d285b3acbade77f9b17e6171dbda69ff4a033878
3 files changed +38 -11
packages/react-reconciler/src/ReactFiberClassComponent.js
+3 -9
@@ -19,13 +19,12 @@ import {
19 } from './ReactFiberFlags';
20 import {
21 debugRenderPhaseSideEffectsForStrictMode,
22 - disableDefaultPropsExceptForClasses,
22 disableLegacyContext,
24 - disableStringRefs,
23 enableDebugTracing,
24 + enableSchedulingProfiler,
25 enableLazyContextPropagation,
26 enableRefAsProp,
28 - enableSchedulingProfiler,
27 + disableDefaultPropsExceptForClasses,
28 } from 'shared/ReactFeatureFlags';
29 import ReactStrictModeWarnings from './ReactStrictModeWarnings';
30 import {isMounted} from './ReactFiberTreeReflection';
@@ -820,12 +819,7 @@ function mountClassInstance(
819 const instance = workInProgress.stateNode;
820 instance.props = newProps;
821 instance.state = workInProgress.memoizedState;
823 - if (!disableStringRefs) {
824 - // When string refs are used in create-react-class legacy components,
825 - // we need to make refs writable unless we patch all such copies of the
826 - // class code that sets to a frozen emptyObject.
827 - instance.refs = {};
828 - }
822 + instance.refs = {};
823
824 initializeUpdateQueue(workInProgress);
825
packages/react-reconciler/src/__tests__/ReactFiberRefs-test.js
+27
@@ -162,4 +162,31 @@ describe('ReactFiberRefs', () => {
162 await act(() => root.render(<App />));
163 expect(app.refs.div.prop).toBe('Hello!');
164 });
165 +
166 + test('class refs are initialized to a frozen shared object', async () => {
167 + const refsCollection = new Set();
168 + class Component extends React.Component {
169 + constructor(props) {
170 + super(props);
171 + refsCollection.add(this.refs);
172 + }
173 + render() {
174 + return <div />;
175 + }
176 + }
177 +
178 + const root = ReactNoop.createRoot();
179 + await act(() =>
180 + root.render(
181 + <>
182 + <Component />
183 + <Component />
184 + </>,
185 + ),
186 + );
187 +
188 + expect(refsCollection.size).toBe(1);
189 + const refsInstance = Array.from(refsCollection)[0];
190 + expect(Object.isFrozen(refsInstance)).toBe(__DEV__);
191 + });
192 });
packages/react/src/ReactBaseClasses.js
+8 -2
@@ -8,13 +8,19 @@
8 import ReactNoopUpdateQueue from './ReactNoopUpdateQueue';
9 import assign from 'shared/assign';
10
11 +const emptyObject = {};
12 +if (__DEV__) {
13 + Object.freeze(emptyObject);
14 +}
15 +
16 /**
17 * Base class helpers for the updating state of a component.
18 */
19 function Component(props, context, updater) {
20 this.props = props;
21 this.context = context;
17 - this.refs = {};
22 + // If a component has string refs, we will assign a different object later.
23 + this.refs = emptyObject;
24 // We initialize the default updater but the real one gets injected by the
25 // renderer.
26 this.updater = updater || ReactNoopUpdateQueue;
@@ -127,7 +133,7 @@ function PureComponent(props, context, updater) {
133 this.props = props;
134 this.context = context;
135 // If a component has string refs, we will assign a different object later.
130 - this.refs = {};
136 + this.refs = emptyObject;
137 this.updater = updater || ReactNoopUpdateQueue;
138 }
139