1
1
import { options as _options } from 'preact' ;
2
+ import { SKIP_CHILDREN } from '../../src/constants' ;
2
3
3
4
const ObjectIs = Object . is ;
4
5
@@ -26,6 +27,7 @@ let oldAfterDiff = options.diffed;
26
27
let oldCommit = options . _commit ;
27
28
let oldBeforeUnmount = options . unmount ;
28
29
let oldRoot = options . _root ;
30
+ let oldAfterRender = options . _afterRender ;
29
31
30
32
// We take the minimum timeout for requestAnimationFrame to ensure that
31
33
// the callback is invoked after the next frame. 35ms is based on a 30hz
@@ -60,10 +62,7 @@ options._render = vnode => {
60
62
hooks . _pendingEffects = [ ] ;
61
63
currentComponent . _renderCallbacks = [ ] ;
62
64
hooks . _list . forEach ( hookItem => {
63
- if ( hookItem . _nextValue ) {
64
- hookItem . _value = hookItem . _nextValue ;
65
- }
66
- hookItem . _pendingArgs = hookItem . _nextValue = undefined ;
65
+ hookItem . _pendingArgs = undefined ;
67
66
} ) ;
68
67
} else {
69
68
hooks . _pendingEffects . forEach ( invokeCleanup ) ;
@@ -186,19 +185,13 @@ export function useReducer(reducer, initialState, init) {
186
185
const hookState = getHookState ( currentIndex ++ , 2 ) ;
187
186
hookState . _reducer = reducer ;
188
187
if ( ! hookState . _component ) {
188
+ hookState . _actions = [ ] ;
189
189
hookState . _value = [
190
190
! init ? invokeOrReturn ( undefined , initialState ) : init ( initialState ) ,
191
191
192
192
action => {
193
- const currentValue = hookState . _nextValue
194
- ? hookState . _nextValue [ 0 ]
195
- : hookState . _value [ 0 ] ;
196
- const nextValue = hookState . _reducer ( currentValue , action ) ;
197
-
198
- if ( ! ObjectIs ( currentValue , nextValue ) ) {
199
- hookState . _nextValue = [ nextValue , hookState . _value [ 1 ] ] ;
200
- hookState . _component . setState ( { } ) ;
201
- }
193
+ hookState . _actions . push ( action ) ;
194
+ hookState . _component . setState ( { } ) ;
202
195
}
203
196
] ;
204
197
@@ -207,75 +200,55 @@ export function useReducer(reducer, initialState, init) {
207
200
if ( ! currentComponent . _hasScuFromHooks ) {
208
201
currentComponent . _hasScuFromHooks = true ;
209
202
let prevScu = currentComponent . shouldComponentUpdate ;
210
- const prevCWU = currentComponent . componentWillUpdate ;
211
-
212
- // If we're dealing with a forced update `shouldComponentUpdate` will
213
- // not be called. But we use that to update the hook values, so we
214
- // need to call it.
215
- currentComponent . componentWillUpdate = function ( p , s , c ) {
216
- if ( this . _force ) {
217
- let tmp = prevScu ;
218
- // Clear to avoid other sCU hooks from being called
219
- prevScu = undefined ;
220
- updateHookState ( p , s , c ) ;
221
- prevScu = tmp ;
222
- }
223
-
224
- if ( prevCWU ) prevCWU . call ( this , p , s , c ) ;
203
+
204
+ currentComponent . shouldComponentUpdate = ( p , s , c ) => {
205
+ return prevScu
206
+ ? prevScu . call ( this , p , s , c ) || hookState . _actions . length
207
+ : hookState . _actions . length ;
225
208
} ;
209
+ }
210
+ }
226
211
227
- // This SCU has the purpose of bailing out after repeated updates
228
- // to stateful hooks.
229
- // we store the next value in _nextValue[0] and keep doing that for all
230
- // state setters, if we have next states and
231
- // all next states within a component end up being equal to their original state
232
- // we are safe to bail out for this specific component.
233
- /**
234
- *
235
- * @type {import('./internal').Component["shouldComponentUpdate"] }
236
- */
237
- // @ts -ignore - We don't use TS to downtranspile
238
- // eslint-disable-next-line no-inner-declarations
239
- function updateHookState ( p , s , c ) {
240
- if ( ! hookState . _component . __hooks ) return true ;
241
-
242
- /** @type {(x: import('./internal').HookState) => x is import('./internal').ReducerHookState } */
243
- const isStateHook = x => ! ! x . _component ;
244
- const stateHooks =
245
- hookState . _component . __hooks . _list . filter ( isStateHook ) ;
246
-
247
- const allHooksEmpty = stateHooks . every ( x => ! x . _nextValue ) ;
248
- // When we have no updated hooks in the component we invoke the previous SCU or
249
- // traverse the VDOM tree further.
250
- if ( allHooksEmpty ) {
251
- return prevScu ? prevScu . call ( this , p , s , c ) : true ;
252
- }
253
-
254
- // We check whether we have components with a nextValue set that
255
- // have values that aren't equal to one another this pushes
256
- // us to update further down the tree
257
- let shouldUpdate = hookState . _component . props !== p ;
258
- stateHooks . forEach ( hookItem => {
259
- if ( hookItem . _nextValue ) {
260
- const currentValue = hookItem . _value [ 0 ] ;
261
- hookItem . _value = hookItem . _nextValue ;
262
- hookItem . _nextValue = undefined ;
263
- if ( ! ObjectIs ( currentValue , hookItem . _value [ 0 ] ) )
264
- shouldUpdate = true ;
265
- }
266
- } ) ;
212
+ if ( hookState . _actions . length ) {
213
+ const initialValue = hookState . _value [ 0 ] ;
214
+ hookState . _actions . some ( action => {
215
+ hookState . _value [ 0 ] = hookState . _reducer ( hookState . _value [ 0 ] , action ) ;
216
+ } ) ;
267
217
268
- return prevScu
269
- ? prevScu . call ( this , p , s , c ) || shouldUpdate
270
- : shouldUpdate ;
271
- }
218
+ hookState . _didUpdate = ! ObjectIs ( initialValue , hookState . _value [ 0 ] ) ;
219
+ hookState . _value = [ hookState . _value [ 0 ] , hookState . _value [ 1 ] ] ;
220
+ hookState . _didExecute = true ;
221
+ hookState . _actions = [ ] ;
222
+ }
272
223
273
- currentComponent . shouldComponentUpdate = updateHookState ;
224
+ return hookState . _value ;
225
+ }
226
+
227
+ options . _afterRender = ( newVNode , oldVNode ) => {
228
+ if ( newVNode . _component && newVNode . _component . __hooks ) {
229
+ const hooks = newVNode . _component . __hooks . _list ;
230
+ const stateHooksThatExecuted = hooks . filter (
231
+ /** @type {(x: import('./internal').HookState) => x is import('./internal').ReducerHookState } */
232
+ // @ts -expect-error
233
+ x => x . _component && x . _didExecute
234
+ ) ;
235
+
236
+ if (
237
+ stateHooksThatExecuted . length &&
238
+ ! stateHooksThatExecuted . some ( x => x . _didUpdate ) &&
239
+ oldVNode . props === newVNode . props
240
+ ) {
241
+ newVNode . _component . __hooks . _pendingEffects = [ ] ;
242
+ newVNode . _flags |= SKIP_CHILDREN ;
274
243
}
244
+
245
+ stateHooksThatExecuted . some ( hook => {
246
+ hook . _didExecute = hook . _didUpdate = false ;
247
+ } ) ;
275
248
}
276
249
277
- return hookState . _nextValue || hookState . _value ;
278
- }
250
+ if ( oldAfterRender ) oldAfterRender ( newVNode , oldVNode ) ;
251
+ } ;
279
252
280
253
/**
281
254
* @param {import('./internal').Effect } callback
0 commit comments