这几天产品处在发版阶段,工作比较忙,很久没有更新博客了。不过今天在工作中遇到一个最新版Chrome浏览器的坑,分析解决的过程还比较有意思,在这里记录一下。

问题描述

现在在做的项目,项目历时很长,之前选用的ReactJS的0.13.3版本,而现在ReactJS已经升级版本至0.15版本了,但旧版本代码一直运行得好好的,所以一直没有动力进行升级。不过今天Chrome自动升级至54版本后,ReactJS开始报错了。如下:

unhandledRejection.js:23 Potentially unhandled rejection [2] TypeError: Failed to execute 'insertBefore' on 'Node': parameter 1 is not of type 'Node'.
    at insertChildAt (webpack:///./~/react/lib/DOMChildrenOperations.js?:34:14)
    at Object.processUpdates (webpack:///./~/react/lib/DOMChildrenOperations.js?:106:11)
    at Object.dangerouslyProcessChildrenUpdates (webpack:///./~/react/lib/ReactDOMIDOperations.js?:150:27)
    at Object.wrapper [as processChildrenUpdates] (webpack:///./~/react/lib/ReactPerf.js?:70:21)
    at processQueue (webpack:///./~/react/lib/ReactMultiChild.js?:141:31)
    at ReactDOMComponent.updateChildren (webpack:///./~/react/lib/ReactMultiChild.js?:263:13)
    at ReactDOMComponent._updateDOMChildren (webpack:///./~/react/lib/ReactDOMComponent.js?:470:12)
    at ReactDOMComponent.updateComponent (webpack:///./~/react/lib/ReactDOMComponent.js?:319:10)
    at ReactDOMComponent.receiveComponent (webpack:///./~/react/lib/ReactDOMComponent.js?:303:10)
    at Object.receiveComponent (webpack:///./~/react/lib/ReactReconciler.js?:97:22)

跟踪了下调用栈,发现问题出在ReactJS操作DOM的代码处

DOMChildrenOperations.js的105行处

  case ReactMultiChildUpdateTypes.INSERT_MARKUP:
          insertChildAt(
            update.parentNode,
            renderedMarkup[update.markupIndex],
            update.toIndex
          );
          break;

这里查看一下update.markupIndex竟然是NaN。继续跟踪ReactMultiChildUpdateTypes.INSERT_MARKUP类型的update是在哪里生成的,于是找到以下代码:

ReactMultiChild.js的40行处

/**
 * Queue of markup to be rendered.
 *
 * @type {array<string>}
 * @private
 */
var markupQueue = [];

/**
 * Enqueues markup to be rendered and inserted at a supplied index.
 *
 * @param {string} parentID ID of the parent component.
 * @param {string} markup Markup that renders into an element.
 * @param {number} toIndex Destination index.
 * @private
 */
function enqueueMarkup(parentID, markup, toIndex) {
  // NOTE: Null values reduce hidden classes.
  updateQueue.push({
    parentID: parentID,
    parentNode: null,
    type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
    markupIndex: markupQueue.push(markup) - 1,
    textContent: null,
    fromIndex: null,
    toIndex: toIndex
  });
}

这里已经标明了生成的update的markupIndex为markupQueue.push(markup) - 1,照理说这肯定不会为NaN的。于是修改代码打印出值看一下。

function enqueueMarkup(parentID, markup, toIndex) {
  var markupIndex = markupQueue.push(markup) - 1;
  console.log(markupIndex);
  // NOTE: Null values reduce hidden classes.
  updateQueue.push({
    parentID: parentID,
    parentNode: null,
    type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
    markupIndex: markupIndex,
    textContent: null,
    fromIndex: null,
    toIndex: toIndex
  });
}

发现竟真的为NaN了,看来应该是Chrome的新版本bug了。为了规避问题,简单修改了下代码后,问题解决:

function enqueueMarkup(parentID, markup, toIndex) {
  var markupIndex = markupQueue.push(markup) - 1;
  if(isNaN(markupIndex)) {
    markupIndex = markupQueue.length - 1;
  }
  // NOTE: Null values reduce hidden classes.
  updateQueue.push({
    parentID: parentID,
    parentNode: null,
    type: ReactMultiChildUpdateTypes.INSERT_MARKUP,
    markupIndex: markupIndex,
    textContent: null,
    fromIndex: null,
    toIndex: toIndex
  });
}

没有改变原来逻辑的本意,仅仅处理了下markupIndex为NaN的情况。

进一步分析

在Chrome的问题列表上搜索了下,果然找到这个问题

总结

ReactJS的源码还挺复杂的,特别是通过虚拟DOM树操作真正DOM那一段。有问题也不要紧,打开Chrome开发者工具,仔细分析还是可以找到问题发生的原因的。