对于redux reducer哪个更快:switch还是map?



redux文档在推荐reducer的最佳实践时,提到重构掉switch语句并用字典/映射动作到处理程序来替换它们,因此,而不是:

switch(action) {
case 'action1':
doAction1(payload);
case 'action2':
doActions2(payload);
}

你会有这样的东西:

var handlers = {
'action1': doAction1,
'action2': doAction2,
}
handlers[action](payload);

(见https://redux.js.org/usage/structuring-reducers/refactoring-reducer-example)

我可以看到字典方法更易于阅读。我想知道这是否是首选的唯一原因?还是说,字典的性能也优于开关?

对于任何实际应用程序的性能来说,这绝对无关紧要。

也就是说,你也不应该考虑它,而是使用官方的Redux工具包,这是官方推荐的编写Redux的方式已经有两年了-在那里你只需要使用createSlice函数,它会取一个case reducer映射,将它们包装在immer中,并为你做很多其他方便的事情。

对于Redux Toolkit的快速概述,请查看https://redux.js.org/tutorials/fundamentals/part-8-modern-redux

有关"现代redux"的完整教程,请查看https://redux.js.org/tutorials/essentials/part-1-overview-concepts

有些人在意识形态上反对切换语句,部分原因是可能会意外丢失break语句,并且有失败的情况。在redux reducer的情况下,这不是一个问题,因为每个case都返回新的状态,所以我个人对这里的开关没有问题,我甚至不确定我是否同意字典是任何"更干净"。

WRT性能,我会选择风格上首选的选项,因为在99.9%的redux情况下,您不会频繁地更新状态,因此这些微小的差异不会产生影响。但是好吧,让我们试试,看看哪个执行得更快:

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

所以switch赢了,虽然不是很大的优势。但是,在实践中您永远不会注意到它,所以从可读性的角度来看,请使用您喜欢的名称。

这是我看到的在2022年Chrome中运行您的确切代码:

switch: 304.3369140625 ms
map: 187.68896484375 ms

你还把地图的建造包括在你的计时中。在这种情况下,它并不重要,因为你的循环是如此之大,但它是一个触摸误导。

这取决于你的用例,但是对于大的条件集,映射通常会更快。在不同的情况下,差异会更加明显——这可能对一些人很重要,对另一些人则无关紧要。在c++中,这也是完全不同的,因为switch语句可以由编译器优化(在某些情况下,它将构建类似二进制的键搜索),所以要小心泛化。

对于较小的switch语句,差异几乎可以忽略不计。在react中,我更关心为不同的子状态部分访问的switch语句的数量。由于Redux不知道什么动作类型与特定的reducer相关联,因此它执行给定状态下所有可能的reducer。例如,如果有20个reducer,可能只有3个响应特定的动作类型,但所有的都被评估。如果您通过映射将其按动作类型映射,则可以通过单个映射查找产生3个相关的reducer。也许在Redux/React中有一种聪明的方法来优化这一点,但这并不是交换机的错,而是架构的错(也许是过多的reducer使用,这可能是特定应用程序的设计问题)。

最新更新