11import { options as _options } from 'preact' ;
2+ import { SKIP_CHILDREN } from '../../src/constants' ;
23
34const ObjectIs = Object . is ;
45
@@ -26,6 +27,7 @@ let oldAfterDiff = options.diffed;
2627let oldCommit = options . _commit ;
2728let oldBeforeUnmount = options . unmount ;
2829let oldRoot = options . _root ;
30+ let oldAfterRender = options . _afterRender ;
2931
3032// We take the minimum timeout for requestAnimationFrame to ensure that
3133// the callback is invoked after the next frame. 35ms is based on a 30hz
@@ -60,10 +62,7 @@ options._render = vnode => {
6062 hooks . _pendingEffects = [ ] ;
6163 currentComponent . _renderCallbacks = [ ] ;
6264 hooks . _list . forEach ( hookItem => {
63- if ( hookItem . _nextValue ) {
64- hookItem . _value = hookItem . _nextValue ;
65- }
66- hookItem . _pendingArgs = hookItem . _nextValue = undefined ;
65+ hookItem . _pendingArgs = undefined ;
6766 } ) ;
6867 } else {
6968 hooks . _pendingEffects . forEach ( invokeCleanup ) ;
@@ -186,19 +185,13 @@ export function useReducer(reducer, initialState, init) {
186185 const hookState = getHookState ( currentIndex ++ , 2 ) ;
187186 hookState . _reducer = reducer ;
188187 if ( ! hookState . _component ) {
188+ hookState . _actions = [ ] ;
189189 hookState . _value = [
190190 ! init ? invokeOrReturn ( undefined , initialState ) : init ( initialState ) ,
191191
192192 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 ( { } ) ;
202195 }
203196 ] ;
204197
@@ -207,75 +200,55 @@ export function useReducer(reducer, initialState, init) {
207200 if ( ! currentComponent . _hasScuFromHooks ) {
208201 currentComponent . _hasScuFromHooks = true ;
209202 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 ;
225208 } ;
209+ }
210+ }
226211
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+ } ) ;
267217
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+ }
272223
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 ;
274243 }
244+
245+ stateHooksThatExecuted . some ( hook => {
246+ hook . _didExecute = hook . _didUpdate = false ;
247+ } ) ;
275248 }
276249
277- return hookState . _nextValue || hookState . _value ;
278- }
250+ if ( oldAfterRender ) oldAfterRender ( newVNode , oldVNode ) ;
251+ } ;
279252
280253/**
281254 * @param {import('./internal').Effect } callback
0 commit comments