From b5292609da273ae257b97651d05e4c62c74941f9 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 27 Sep 2015 17:02:32 +0300 Subject: [PATCH 1/2] Do not recompute states unless necessary --- src/devTools.js | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/devTools.js b/src/devTools.js index 4704438a8d..fa07948625 100644 --- a/src/devTools.js +++ b/src/devTools.js @@ -95,6 +95,7 @@ function liftReducer(reducer, initialState) { * Manages how the DevTools actions modify the DevTools state. */ return function liftedReducer(liftedState = initialLiftedState, liftedAction) { + let shouldRecomputeStates = true; let { committedState, stagedActions, @@ -131,6 +132,8 @@ function liftReducer(reducer, initialState) { break; case ActionTypes.JUMP_TO_STATE: currentStateIndex = liftedAction.index; + // Optimization: we know the history has not changed. + shouldRecomputeStates = false; break; case ActionTypes.SWEEP: stagedActions = stagedActions.filter((_, i) => !skippedActions[i]); @@ -142,8 +145,21 @@ function liftReducer(reducer, initialState) { if (currentStateIndex === stagedActions.length - 1) { currentStateIndex++; } + stagedActions = [...stagedActions, liftedAction.action]; timestamps = [...timestamps, liftedAction.timestamp]; + + // Optimization: we know that the past has not changed. + shouldRecomputeStates = false; + // Instead of recomputing the states, append the next one. + const previousEntry = computedStates[computedStates.length - 1]; + const nextEntry = computeNextEntry( + reducer, + liftedAction.action, + previousEntry.state, + previousEntry.error + ); + computedStates = [...computedStates, nextEntry]; break; case ActionTypes.SET_MONITOR_STATE: monitorState = liftedAction.monitorState; @@ -159,12 +175,14 @@ function liftReducer(reducer, initialState) { break; } - computedStates = recomputeStates( - reducer, - committedState, - stagedActions, - skippedActions - ); + if (shouldRecomputeStates) { + computedStates = recomputeStates( + reducer, + committedState, + stagedActions, + skippedActions + ); + } return { committedState, From 092bb66d03b5200cc2818011b0c09acb33620cf9 Mon Sep 17 00:00:00 2001 From: Dan Abramov Date: Sun, 27 Sep 2015 20:13:38 +0300 Subject: [PATCH 2/2] Add performance regression tests --- test/devTools.spec.js | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/test/devTools.spec.js b/test/devTools.spec.js index 672fb8b97a..af47f858c8 100644 --- a/test/devTools.spec.js +++ b/test/devTools.spec.js @@ -212,4 +212,35 @@ describe('devTools', () => { storeWithBug.dispatch({ type: 'SET_UNDEFINED' }); expect(storeWithBug.getState()).toBe(2); }); + + it('should not recompute states on every action', () => { + let reducerCalls = 0; + let monitoredStore = devTools()(createStore)(() => reducerCalls++); + expect(reducerCalls).toBe(1); + monitoredStore.dispatch({ type: 'INCREMENT' }); + monitoredStore.dispatch({ type: 'INCREMENT' }); + monitoredStore.dispatch({ type: 'INCREMENT' }); + expect(reducerCalls).toBe(4); + }); + + it('should not recompute states when jumping to state', () => { + let reducerCalls = 0; + let monitoredStore = devTools()(createStore)(() => reducerCalls++); + let monitoredDevToolsStore = monitoredStore.devToolsStore; + + expect(reducerCalls).toBe(1); + monitoredStore.dispatch({ type: 'INCREMENT' }); + monitoredStore.dispatch({ type: 'INCREMENT' }); + monitoredStore.dispatch({ type: 'INCREMENT' }); + expect(reducerCalls).toBe(4); + + monitoredDevToolsStore.dispatch(ActionCreators.jumpToState(0)); + expect(reducerCalls).toBe(4); + + monitoredDevToolsStore.dispatch(ActionCreators.jumpToState(1)); + expect(reducerCalls).toBe(4); + + monitoredDevToolsStore.dispatch(ActionCreators.jumpToState(3)); + expect(reducerCalls).toBe(4); + }); });