Skip to content

Commit 86944f4

Browse files
authored
Merge pull request #5203 from preactjs/agent/tree-shake-compat-feature-hooks
Tree-shake feature-specific compat hooks
2 parents e881e7e + 0ed7f4c commit 86944f4

2 files changed

Lines changed: 195 additions & 177 deletions

File tree

compat/src/render.js

Lines changed: 58 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ import { assign, IS_NON_DIMENSIONAL } from './util';
2424
export const REACT_ELEMENT_TYPE = Symbol.for('react.element');
2525

2626
const MODE_HYDRATE = 1 << 5;
27+
let currentComponent, hydrationRoot, renderTrackingInitialized;
2728

2829
const CAMEL_PROPS =
2930
/^(?:accent|alignment|arabic|baseline|cap|clip(?!PathU)|color|dominant|fill|flood|font|glyph(?!R)|horiz|image(!S)|letter|lighting|marker(?!H|W|U)|overline|paint|pointer|shape|stop|strikethrough|stroke|text(?!L)|transform|underline|unicode|units|v|vector|vert|word|writing|x(?!C))[A-Z]/;
@@ -35,47 +36,45 @@ const IS_DOM = typeof document != 'undefined';
3536
* on a high level this cuts out the warnings, ... and attempts a smaller implementation
3637
* @typedef {{ _value: any; _getSnapshot: () => any }} Store
3738
*/
38-
export function useSyncExternalStore(
39-
subscribe,
40-
getSnapshot,
41-
getServerSnapshot
42-
) {
43-
const serverRendering = options._skipEffects || hydrationRoot;
44-
const value = serverRendering
45-
? (getServerSnapshot || getSnapshot)()
46-
: getSnapshot();
47-
48-
/**
49-
* @typedef {{ _instance: Store }} StoreRef
50-
* @type {[StoreRef, (store: StoreRef) => void]}
51-
*/
52-
const [{ _instance }, forceUpdate] = useState({
53-
_instance: { _value: value, _getSnapshot: getSnapshot }
54-
});
55-
56-
useLayoutEffect(() => {
57-
_instance._value = value;
58-
_instance._getSnapshot = getSnapshot;
39+
export const useSyncExternalStore = /* @__PURE__ */ initRenderTracking(
40+
function useSyncExternalStore(subscribe, getSnapshot, getServerSnapshot) {
41+
const serverRendering = options._skipEffects || hydrationRoot;
42+
const value = serverRendering
43+
? (getServerSnapshot || getSnapshot)()
44+
: getSnapshot();
45+
46+
/**
47+
* @typedef {{ _instance: Store }} StoreRef
48+
* @type {[StoreRef, (store: StoreRef) => void]}
49+
*/
50+
const [{ _instance }, forceUpdate] = useState({
51+
_instance: { _value: value, _getSnapshot: getSnapshot }
52+
});
5953

60-
if (didSnapshotChange(_instance)) {
61-
forceUpdate({ _instance });
62-
}
63-
}, [subscribe, value, getSnapshot]);
54+
useLayoutEffect(() => {
55+
_instance._value = value;
56+
_instance._getSnapshot = getSnapshot;
6457

65-
useEffect(() => {
66-
if (didSnapshotChange(_instance)) {
67-
forceUpdate({ _instance });
68-
}
58+
if (didSnapshotChange(_instance)) {
59+
forceUpdate({ _instance });
60+
}
61+
}, [subscribe, value, getSnapshot]);
6962

70-
return subscribe(() => {
63+
useEffect(() => {
7164
if (didSnapshotChange(_instance)) {
7265
forceUpdate({ _instance });
7366
}
74-
});
75-
}, [subscribe]);
7667

77-
return value;
78-
}
68+
return subscribe(() => {
69+
if (didSnapshotChange(_instance)) {
70+
forceUpdate({ _instance });
71+
}
72+
});
73+
}, [subscribe]);
74+
75+
return value;
76+
}
77+
);
7978

8079
/** @type {(inst: Store) => boolean} */
8180
function didSnapshotChange(inst) {
@@ -310,17 +309,6 @@ options.vnode = vnode => {
310309
if (oldVNodeHook) oldVNodeHook(vnode);
311310
};
312311

313-
// Only needed for react-relay
314-
let currentComponent, hydrationRoot;
315-
const oldBeforeRender = options._render;
316-
options._render = function (vnode) {
317-
if (oldBeforeRender) {
318-
oldBeforeRender(vnode);
319-
}
320-
if (vnode._flags & MODE_HYDRATE) hydrationRoot = vnode;
321-
currentComponent = vnode._component;
322-
};
323-
324312
const oldDiffed = options.diffed;
325313
/** @type {(vnode: import('./internal').VNode) => void} */
326314
options.diffed = function (vnode) {
@@ -339,19 +327,39 @@ options.diffed = function (vnode) {
339327
) {
340328
dom.value = props.value == null ? '' : props.value;
341329
}
342-
343-
currentComponent = null;
344-
if (hydrationRoot == vnode) hydrationRoot = null;
345330
};
346331

332+
// Only needed for react-relay and useSyncExternalStore hydration.
333+
function initRenderTracking(value) {
334+
if (!renderTrackingInitialized) {
335+
renderTrackingInitialized = true;
336+
337+
const oldBeforeRender = options._render;
338+
options._render = vnode => {
339+
if (oldBeforeRender) oldBeforeRender(vnode);
340+
if (vnode._flags & MODE_HYDRATE) hydrationRoot = vnode;
341+
currentComponent = vnode._component;
342+
};
343+
344+
const oldDiffed = options.diffed;
345+
options.diffed = vnode => {
346+
if (oldDiffed) oldDiffed(vnode);
347+
currentComponent = null;
348+
if (hydrationRoot == vnode) hydrationRoot = null;
349+
};
350+
}
351+
352+
return value;
353+
}
354+
347355
/**
348356
* Read the value of a Promise (suspending while pending) or a Context.
349357
* Unlike other hooks, `use` may be called conditionally.
350358
* @template T
351359
* @param {(Promise<T> & { status?: string, value?: T, reason?: any }) | import('../../src/internal').PreactContext} resource
352360
* @returns {T}
353361
*/
354-
export const use = resource => {
362+
export const use = /* @__PURE__ */ initRenderTracking(function use(resource) {
355363
// A Context is a function without a `then`, a thenable has one.
356364
if (resource.then) {
357365
if (resource.status == 'fulfilled') return resource.value;
@@ -383,7 +391,7 @@ export const use = resource => {
383391
provider.sub(currentComponent);
384392
}
385393
return provider.props.value;
386-
};
394+
});
387395

388396
// This is a very very private internal function for React it
389397
// is used to sort-of do runtime dependency injection.

0 commit comments

Comments
 (0)