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瀏覽器,圈圈轉一下消失了,數據正常加載渲染。大功告成,歐耶。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章