es6+react+mobx之Reaction doesn't converge to a stable state after 100 iterations

前言

近期项目开发技术开始转向react。对于react中的state管理,目前主要使用redux或mobx来进行管理。在作出比较后,选择mobx来进行state的管理微笑。

开展

使用es6+react+mobx,实现需求,疯狂的编写代码奋斗。一个巨大的坑悄悄的在等着踩大哭。

高潮

咦,换个浏览器来跑跑,看看效果如何。满心期待中羡慕。一开始就是使用chrome开发调试,chrome就不用管了。next --》firefox。firefox很给力,一切正常,没毛病。那再来下一个Safari。页面打开,what!!!怎么一直在装圈圈惊讶。说是迟那时快,打开浏览器控制台。一把红色的小叉叉提示出问题了难过。仔细一看控制台输出如下error:Reaction doesn’t converge to a stable state after 100 iterations。。。啥,遍历了一百次,这是什么鬼。其他浏览器都是正常的呀。而且es6转es5也考虑了兼容性,使用了es5-shim。

定位过程

既然出现了,那就得消灭它。一步步得开始漫长的定位过程。是不是非法操作state。于是对有操作state的地方进行详细排查,删除复杂逻辑代码,删除到最后就剩下一个框框了可怜。但是问题依然存在,啊~~~要疯啦骂人。好吧,这肯定不是我的代码写的有问题,我确定,因为哥的代码都快删除光了可怜。既然业务代码没啥问题,那就对引入的模块源码开始质疑了。是不是源码有问题。开启调试模式,重点盯在抛异常的这段源代码上。附上这段源码:

function runReactionsHelper() {
    globalState.isRunningReactions = true;
    var allReactions = globalState.pendingReactions;
    var iterations = 0;
    while (allReactions.length > 0) {
        if (++iterations === MAX_REACTION_ITERATIONS) {
            resetGlobalState();
            throw new Error(("Reaction doesn't converge to a stable state after " + MAX_REACTION_ITERATIONS + " iterations.")
                + (" Probably there is a cycle in the reactive function: " + allReactions[0]));
        }
        var remainingReactions = allReactions.splice(0);
var remainingReactions = allReactions.splice(0);

for (var i = 0, l = remainingReactions.length; i < l; i++) remainingReactions[i].runReaction(); } globalState.isRunningReactions = false;}

当调试到var remainingReactions = allReactions.splice(0);执行完这行代码后,remainingReactions是空的,length为0再见。不对呀,remainingReactions的值应该是allReactions的所有值。但为什么是空的。那就进入splice方法中看看。这会让人感到奇怪疑问。splice方法是浏览器自带方法,怎么进的去。没错,是进不去,但是浏览器的版本稍微低一点。那就有可能了。我的Safari就是稍微低了那么一点(8.1),才出现此文中提到的问题。版本9以上没这个问题。这就是模块es5-shim干的活了。好那我们进去看看,一探究竟奋斗。进去后发现问题了。附上相应的源码:

splice: function splice(start, deleteCount) {
            var O = ES.ToObject(this);
            var A = [];
            var len = ES.ToUint32(O.length);
            var relativeStart = ES.ToInteger(start);
            var actualStart = relativeStart < 0 ? max((len + relativeStart), 0) : min(relativeStart, len);
            var actualDeleteCount = min(max(ES.ToInteger(deleteCount), 0), len - actualStart);

            var k = 0;
            var from;
            while (k < actualDeleteCount) {
                from = $String(actualStart + k);
                if (owns(O, from)) {
                    A[k] = O[from];
                }
                k += 1;
            }
...
}

咦,这行代码var actualDeleteCount = min(max(ES.ToInteger(deleteCount), 0), len - actualStart);有问题呀,为什么是取最小值min。当deleteCount这个参数不传时,那么actualDeleteCount得到的结果只能是0,也就意味着不会删除。且返回值是空。怎么会这样了???带着这个问题开启谷歌之旅,最终找到了答案。es5和es6这两者对splice方法的定义标准有差异、、、。哦买噶等。es5中第二个参数deleteCount不传的话,默认值是0,而es6中默认值是全部。好吧,我服了再见。

结局

这好像并不是源码的锅,那是谁的锅。只能把锅甩给定标准的人了。锅是甩了。但Safari浏览器问题还没解决呀。其实这问题的根源已经定位出来了,那解决方法自然就能找到了得意。解决办法很简单,那就是修改源码,修改谁的源码?那当然是mobx的源码,因为是它使用了splice方法呀。而且第二个参数没添加,那么我们就给加上去大笑。修改文中引入的第一段mobx源代码,修改前:

var remainingReactions = allReactions.splice(0);

修改后:

var remainingReactions = allReactions.splice(0,allReactions.length);

重新编译打包,打开Safari浏览器,圈圈转一下消失了,数据正常加载渲染。大功告成,欧耶。

你可能感兴趣的:(JavaScript专题)