The redux documentation, when recommending best practices for reducer, mentions refactoring away switch statements and replacing them with a dictionary/map of action to handler, so instead of:
switch(action) {
case 'action1':
doAction1(payload);
case 'action2':
doActions2(payload);
}
you'd have something like:
var handlers = {
'action1': doAction1,
'action2': doAction2,
}
handlers[action](payload);
(see https://redux.js.org/usage/structuring-reducers/refactoring-reducer-example)
I can see that the dictionary approach is cleaner to read. I am wondering if this is the only reason it is preferred? Or does the dictionary outperform the switch too?
It absolutely does not matter at all for performance in any real application.
That said, you should also not be a position to think about it, but use the official Redux Toolkit instead, which is the officially recommended way of writing Redux for two years now - and there you would just use the createSlice function, which would take a case reducer map, wrap them in immer and do a lot of other convenience things for you.
For a quick overview over Redux Toolkit, please take a look at https://redux.js.org/tutorials/fundamentals/part-8-modern-redux
For a complete tutorial on "modern Redux", take a look at https://redux.js.org/tutorials/essentials/part-1-overview-concepts
Some people have ideological objections to switch statements, partly because of the possibility of missing break statements accidentally and having fall-through cases. In the case of a redux reducer, that's not an issue since each case returns the new state, so I personally have no problem with the switch here, and I'm not even sure I agree the dictionary is any 'cleaner'.
WRT performance, I would go with the stylistically preferred option because in 99.9% of redux cases, you won't be updating state frequently enough for such minor differences to matter. But OK, let's suck it and see which performs faster:
const keys = ['a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z'];
let count = 0;
const increment = () => ++count; // just do something on each action
console.time("switch");
for (let i = 0; i < 10000000; i++) {
const k = keys[Math.floor(Math.random() * keys.length)];
switch (k) {
case 'a':
increment();
break;
case 'b':
increment();
break;
case 'c':
increment();
break;
case 'd':
increment();
break;
case 'e':
increment();
break;
case 'f':
increment();
break;
case 'g':
increment();
break;
case 'h':
increment();
break;
case 'i':
increment();
break;
case 'j':
increment();
break;
case 'k':
increment();
break;
case 'l':
increment();
break;
case 'm':
increment();
break;
case 'n':
increment();
break;
case 'o':
increment();
break;
case 'p':
increment();
break;
case 'q':
increment();
break;
case 'r':
increment();
break;
case 's':
increment();
break;
case 't':
increment();
break;
case 'u':
increment();
break;
case 'v':
increment();
break;
case 'w':
increment();
break;
case 'x':
increment();
break;
case 'y':
increment();
break;
case 'z':
increment();
break;
}
}
console.timeEnd('switch');
console.time('map');
const map = keys.reduce((a, b) => {
a[b] = increment;
return a;
}, {});
for (let i = 0; i < 10000000; i++) {
const k = keys[Math.floor(Math.random() * keys.length)];
map[k]();
}
console.timeEnd('map');
// switch: 529.752ms
// map: 737.213ms
So switch wins, by a significant, though not enormous margin. However, you'll never notice it in practice, so go with the one you prefer from a readability perspective.
This is what I see running your exact code in Chrome in 2022:
switch: 304.3369140625 ms
map: 187.68896484375 ms
You're also including the building of the map in your timing. In this case it is not significant because your loop is so large, but it's a touch misleading.
It depends on your use cases, but a map will often be faster for large condition sets. The differences will be more apparent for different situations-- it may matter greatly for some, and not for others. In C++ this is completely different as well, because switch statements can be optimized by the compiler (it will build binary-like searches of your keys in some cases) so be wary of generalizations.
In cases of smaller switch statements, the difference will almost certainly be negligible. In react, I'm more concerned about the number of switch statements being visited for different substate sections. It appears that since Redux doesn't know what action types are associated with a particular reducer, it executes all possible reducers for a given state. i.e. if there are 20 reducers, maybe only 3 of the respond to a particular action type, but all of them are evaluated. If you instead mapped this by action type via a map, it could be a single map lookup that yields the 3 relevant reducers. Maybe there's a clever way to optimize this in Redux/React, but it's not really the switch's fault, but rather the architecture (and perhaps excessive reducer usage, which could be a design issue with the particular app).