Merge pull request #133 from gaearon/optimize-state-computation

Do not recompute states unless necessary
This commit is contained in:
Dan Abramov 2015-09-27 20:24:00 +03:00
commit 32371664a0
2 changed files with 55 additions and 6 deletions

View File

@ -95,6 +95,7 @@ function liftReducer(reducer, initialState) {
* Manages how the DevTools actions modify the DevTools state. * Manages how the DevTools actions modify the DevTools state.
*/ */
return function liftedReducer(liftedState = initialLiftedState, liftedAction) { return function liftedReducer(liftedState = initialLiftedState, liftedAction) {
let shouldRecomputeStates = true;
let { let {
committedState, committedState,
stagedActions, stagedActions,
@ -131,6 +132,8 @@ function liftReducer(reducer, initialState) {
break; break;
case ActionTypes.JUMP_TO_STATE: case ActionTypes.JUMP_TO_STATE:
currentStateIndex = liftedAction.index; currentStateIndex = liftedAction.index;
// Optimization: we know the history has not changed.
shouldRecomputeStates = false;
break; break;
case ActionTypes.SWEEP: case ActionTypes.SWEEP:
stagedActions = stagedActions.filter((_, i) => !skippedActions[i]); stagedActions = stagedActions.filter((_, i) => !skippedActions[i]);
@ -142,8 +145,21 @@ function liftReducer(reducer, initialState) {
if (currentStateIndex === stagedActions.length - 1) { if (currentStateIndex === stagedActions.length - 1) {
currentStateIndex++; currentStateIndex++;
} }
stagedActions = [...stagedActions, liftedAction.action]; stagedActions = [...stagedActions, liftedAction.action];
timestamps = [...timestamps, liftedAction.timestamp]; 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; break;
case ActionTypes.SET_MONITOR_STATE: case ActionTypes.SET_MONITOR_STATE:
monitorState = liftedAction.monitorState; monitorState = liftedAction.monitorState;
@ -159,12 +175,14 @@ function liftReducer(reducer, initialState) {
break; break;
} }
if (shouldRecomputeStates) {
computedStates = recomputeStates( computedStates = recomputeStates(
reducer, reducer,
committedState, committedState,
stagedActions, stagedActions,
skippedActions skippedActions
); );
}
return { return {
committedState, committedState,

View File

@ -212,4 +212,35 @@ describe('devTools', () => {
storeWithBug.dispatch({ type: 'SET_UNDEFINED' }); storeWithBug.dispatch({ type: 'SET_UNDEFINED' });
expect(storeWithBug.getState()).toBe(2); 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);
});
}); });