@@ -19,6 +19,8 @@ import { Effect, ReactDispatcher } from "./internal";
19
19
20
20
export { signal , computed , batch , effect , Signal , type ReadonlySignal } ;
21
21
22
+ const Empty = Object . freeze ( [ ] ) ;
23
+
22
24
/**
23
25
* Install a middleware into React.createElement to replace any Signals in props with their value.
24
26
* @todo this likely needs to be duplicated for jsx()...
@@ -55,7 +57,6 @@ function createPropUpdater(props: any, prop: string, signal: Signal) {
55
57
*/
56
58
57
59
let finishUpdate : ( ( ) => void ) | undefined ;
58
- const updaterForComponent = new WeakMap < ( ) => void , Effect > ( ) ;
59
60
60
61
function setCurrentUpdater ( updater ?: Effect ) {
61
62
// end tracking for the current update:
@@ -64,13 +65,39 @@ function setCurrentUpdater(updater?: Effect) {
64
65
finishUpdate = updater && updater . _start ( ) ;
65
66
}
66
67
67
- function createUpdater ( update : ( ) => void ) {
68
+ /**
69
+ * A redux-like store whose store value is a positive 32bit integer (a 'version') to be used with useSyncExternalStore API.
70
+ * React (current owner) subscribes to this store and gets a snapshot of the current 'version'.
71
+ * Whenever the 'version' changes, we tell React it's time to update the component (call 'onStoreChange').
72
+ *
73
+ * How we achieve this is by creating a binding with an 'effect', when the `effect._callback' is called,
74
+ * we update our store version and tell React to re-render the component ([1] We don't really care when/how React does it).
75
+ *
76
+ * [1]
77
+ * @see https://reactjs.org/docs/hooks-reference.html#usesyncexternalstore
78
+ * @see https://github.com/reactwg/react-18/discussions/86
79
+ */
80
+ function createEffectStore ( ) {
68
81
let updater ! : Effect ;
82
+ let version = 0 ;
83
+
69
84
effect ( function ( this : Effect ) {
70
85
updater = this ;
71
86
} ) ;
72
- updater . _callback = update ;
73
- return updater ;
87
+
88
+ return {
89
+ updater,
90
+ subscribe ( onStoreChange : ( ) => void ) {
91
+ updater . _callback = function ( ) {
92
+ version = ( version + 1 ) & ~ ( 1 << 31 ) ;
93
+ onStoreChange ( ) ;
94
+ } ;
95
+ return function _unsubscribe ( ) { } ;
96
+ } ,
97
+ getSnapshot ( ) {
98
+ return version ;
99
+ } ,
100
+ } ;
74
101
}
75
102
76
103
/**
@@ -97,30 +124,31 @@ Object.defineProperties(Signal.prototype, {
97
124
98
125
// Track the current dispatcher (roughly equiv to current component impl)
99
126
let lock = false ;
100
- const UPDATE = ( ) => ( { } ) ;
101
127
let currentDispatcher : ReactDispatcher ;
128
+
102
129
Object . defineProperty ( internals . ReactCurrentDispatcher , "current" , {
103
130
get ( ) {
104
131
return currentDispatcher ;
105
132
} ,
106
- set ( api ) {
133
+ set ( api : ReactDispatcher ) {
107
134
currentDispatcher = api ;
108
135
if ( lock ) return ;
109
136
if ( api && ! isInvalidHookAccessor ( api ) ) {
110
- // prevent re-injecting useReducer when the Dispatcher
111
- // context changes to run the reducer callback:
137
+ // prevent re-injecting useMemo & useSyncExternalStore when the Dispatcher
138
+ // context changes.
112
139
lock = true ;
113
- const rerender = api . useReducer ( UPDATE , { } ) [ 1 ] ;
140
+
141
+ const store = api . useMemo ( createEffectStore , Empty ) ;
142
+
143
+ api . useSyncExternalStore (
144
+ store . subscribe ,
145
+ store . getSnapshot ,
146
+ store . getSnapshot
147
+ ) ;
148
+
114
149
lock = false ;
115
150
116
- let updater = updaterForComponent . get ( rerender ) ;
117
- if ( ! updater ) {
118
- updater = createUpdater ( rerender ) ;
119
- updaterForComponent . set ( rerender , updater ) ;
120
- } else {
121
- updater . _callback = rerender ;
122
- }
123
- setCurrentUpdater ( updater ) ;
151
+ setCurrentUpdater ( store . updater ) ;
124
152
} else {
125
153
setCurrentUpdater ( ) ;
126
154
}
@@ -141,13 +169,13 @@ function isInvalidHookAccessor(api: ReactDispatcher) {
141
169
}
142
170
143
171
export function useSignal < T > ( value : T ) {
144
- return useMemo ( ( ) => signal < T > ( value ) , [ ] ) ;
172
+ return useMemo ( ( ) => signal < T > ( value ) , Empty ) ;
145
173
}
146
174
147
175
export function useComputed < T > ( compute : ( ) => T ) {
148
176
const $compute = useRef ( compute ) ;
149
177
$compute . current = compute ;
150
- return useMemo ( ( ) => computed < T > ( ( ) => $compute . current ( ) ) , [ ] ) ;
178
+ return useMemo ( ( ) => computed < T > ( ( ) => $compute . current ( ) ) , Empty ) ;
151
179
}
152
180
153
181
export function useSignalEffect ( cb : ( ) => void | ( ( ) => void ) ) {
@@ -158,5 +186,5 @@ export function useSignalEffect(cb: () => void | (() => void)) {
158
186
return effect ( ( ) => {
159
187
return callback . current ( ) ;
160
188
} ) ;
161
- } , [ ] ) ;
189
+ } , Empty ) ;
162
190
}
0 commit comments