Your time is limited,
so don't waste it living someone else's life.

redux中出现同类名action导致的bug

最近同事发现了一个历史遗留bug,bug的原因是redux store派发了同名action导致的。因为现在习惯了模块化开发,通常下意思会认为只要是不同模块里导出来的变量被用到不同的页面里,那彼此就没啥关系了。所以刚看到这个bug还是愣了一下的,虽然是个简单的bug。应了那句话(我说的),难的东西只有两种,一种是真的难,一种是实在简单到过于出乎意料(以至于往往你会想不到)。

都知道redux会管理一个状态机(store),我们可以通过store.dispatch方法来派发不同的action,redux会根据具体派发的action来决定使用何种reducer来更新state(更新后的state我们可以通过store.getState方法获得)。我们碰到的场景简单化后可以用下面这种方式来描述:

一、代码本身逻辑

在模块A中,我们定义了一个actionA( { type: 'A', payload: productName } )(这里的productName是取当前页产品名然后通过调用生成actionA的函数(action generator)来传入的),在A1页面派发该action后会将state.moduleA.productName的值修改为 productName 并通过localStorage进行本地存储,然后在A2页面,我们通过store.getState().moduleA.productName来获取产品名称并进行后续的购买操作。

在模块B中,我们定义了一个名为actionB( { type: 'B', payload: productName } )(这里的productName是取当前页产品名然后通过调用生成actionA的函数(action generator)来传入的),在A2页面派发该action后会将state.moduleB.productName的值修改为 productName 并通过localStorage进行本地存储,然后在B2页面,我们通过store.getState().moduleB.productName来获取产品名称并进行后续的购买操作。

说明:此处为简化模型,非实际场景,实际场景下如果只是带少量参数且拼接后的完整url长度不至于长到超过get请求url长度限制的情况下,跳到下一个页面时推荐直接通过url传递参数的方式。

二、bug解析

现在我们先访问A1页面派发actionA,然后我们不访问B1页面直接去访问B2页面(比如直接通过修改浏览器地址来访问)。正常来说在B2页面我们要么获取不到产品名称,要么可以获取到的是更早些时候我们访问B1页面时存入本地的产品名称。但是如果actionA和actionB中的 type 字段的值相同时(比如都为 'A' ),我们看看会发生什么。

假设现在我们访问A1页面派发actionA时当前页产品名为’productA1’,因为redux其实不知道什么actionA、actionB的,它只当接收了一个 { type: 'A', payload: productName }(这里productName变量的值为 'productA1' ),然后他就将state.moduleA.productName的值更新为 'productA1' 。但是,因为我们在模块B的代码中也告诉了redux——如果遇到 { type: 'A', payload: productName }这样的action时,更新state.moduleB.productName为 productName。这就导致方位A1页面派发一个 { type: 'A', payload: 'productA1' }后,state.moduleA.productName和state.moduleB.productName的值都被更新成 'productA1' 了。进而当我们直接访问B2页面时,当前页面查询到的产品名为 'productA1' 了,但其实在B2页可能适用的产品名应该是 'productB1' 、 'productB2' 、 'productB3' 等。

三、解决方案

解决方案就一条:避免同名action。

这里所谓的同名action,即用于判断采用何种reducer的“依据”相同的action。在上面例子中,就是指 type 值相同的action,但这只是平时大家比较常用的action key,如果你的action长这种样子: { name: 'actionName', data: data } ,那么同名action就是指 name 值相同的那些action。

避免同名action的措施:在action名中添加模块名前缀起到命名作用域的作用。如上面例子中,我们可以将模块A中的action重命名为:

将B模块中的action重命名为:

 

这样,尽管上面两个action的type值在去掉模块名前缀后均为 'A' ,但是对redux来说已经是完全不同的两个action了,不会出现本文例子中那样的混乱场景了。如果你的模块结果较为复杂,比如模块A1内嵌模块A2,模块A2内嵌模块A3,模块B1内嵌模块B2,模块B2内嵌模块B3。那么只需要在模块名处叠加这些模块名作为模块名前缀即可,比如下面这样:

为保险起见,我们可以约定用对应的模块文件所在的文件夹需要保持与模块名相同,然后定义action的文件名(比如action-types.js)也相同,这样我们就可以在现有的前端编译/构建工具中很方便的加入对action-types中命名是否符合规范的检测了,毕竟手动维护都是存在笔误风险的,或者新员工可能并不知道团队有类似这样的命名约定,这些我们都需要加以预防。

赞(0) 打赏(金额可任意指定)
未经允许不得转载:峰间的云 » redux中出现同类名action导致的bug

评论 抢沙发

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏