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