7

背景

我们的 webapp 是使用官方 react-redux 绑定使用 React 和 Redux 编写的。此 Web 应用程序中使用的另一个主要库是PaperJS。我们最近将其转换为 Redux 应用程序,尽管它使用 React 有一段时间了。

问题

有时刷新(通常是每隔一次刷新)会导致

RangeError: Maximum call stack size exceeded
at String.replace (<anonymous>)
at Object.unescape (KeyEscapeUtils.js:49)
at flattenSingleChildIntoContext (flattenChildren.js:32)
at flattenChildren.js:53
at traverseAllChildrenImpl (traverseAllChildren.js:69)
at traverseAllChildrenImpl (traverseAllChildren.js:85)
at traverseAllChildren (traverseAllChildren.js:157)
at flattenChildren (flattenChildren.js:52)
at ReactDOMComponent._reconcilerUpdateChildren (ReactMultiChild.js:209)
at ReactDOMComponent._updateChildren (ReactMultiChild.js:315)

这是失败的 React 源代码:

return ('' + keySubstring).replace(unescapeRegex, function (match) {
  return unescaperLookup[match];
});

在上下文中:

/**
 * Unescape and unwrap key for human-readable display
 *
 * @param {string} key to unescape.
 * @return {string} the unescaped key.
 */
function unescape(key) {
  var unescapeRegex = /(=0|=2)/g;
  var unescaperLookup = {
    '=0': '=',
    '=2': ':'
  };
  var keySubstring = key[0] === '.' && key[1] === '$' ? key.substring(2) : key.substring(1);

  return ('' + keySubstring).replace(unescapeRegex, function (match) {
    return unescaperLookup[match];
  });
}

这可能表明我在代码中的某个地方滥用了 React,但由于堆栈跟踪不包含对我自己的任何代码的引用,我不确定要查找什么。这似乎是一个无限循环的重新渲染,我怀疑这可能是由于对setState.

问题

我的怀疑可能吗?鉴于我自己的代码库相当广泛,我该如何进一步诊断这个问题?这在 KeyEscapeUtils 中失败是什么意思?

4

2 回答 2

4

我翻遍unescape了React(15.4版)的源代码,只找到了一处用到它的地方。该文件是react/lib/flattenChildren.js

function flattenSingleChildIntoContext(traverseContext, child, name, selfDebugID) {
  // We found a component instance.
  if (traverseContext && typeof traverseContext === 'object') {
    var result = traverseContext;
    var keyUnique = result[name] === undefined;
    if (process.env.NODE_ENV !== 'production') {
      if (!ReactComponentTreeHook) {
        ReactComponentTreeHook = require('./ReactComponentTreeHook');
      }
      if (!keyUnique) {
        process.env.NODE_ENV !== 'production' ? warning(false, 'flattenChildren(...): Encountered two children with the same key, ' + '`%s`. Child keys must be unique; when two children share a key, only ' + 'the first child will be used.%s', KeyEscapeUtils.unescape(name), ReactComponentTreeHook.getStackAddendumByID(selfDebugID)) : void 0;
      }
    }
    if (keyUnique && child != null) {
      result[name] = child;
    }
  }
}

它在flattenSingleChildIntoContext- 正是它在堆栈跟踪中的显示方式。有问题的行尝试显示警告:

process.env.NODE_ENV !== 'production' ? warning(false, 'flattenChildren(...): Encountered two children with the same key, ' + '`%s`. Child keys must be unique; when two children share a key, only ' + 'the first child will be used.%s', KeyEscapeUtils.unescape(name), ReactComponentTreeHook.getStackAddendumByID(selfDebugID)) : void 0;

这意味着最大调用堆栈错误仅发生在开发模式中。问题是您在某处使用相同的键渲染两个或多个元素。要知道关键是什么,您可以在 Chrome 开发工具的这一行中使用断点。或者您可以在此处添加console.log并抛出异常,以便它在此处停止执行:

  if (!keyUnique) {
    console.log('key is not unique', name);
    throw new Error('Key is not unique');
  }

如果您以这种方式获得密钥,您将能够找到具有重复密钥的元素的位置。

于 2017-04-01T16:30:12.400 回答
2

我认为它失败的地方并不重要,因为您似乎陷入了无限循环,而这恰好是超出调用堆栈限制的地方。

为了进行诊断,我会尝试在 chrome 开发工具中打开“异常暂停”(如果还没有的话),并在暂停时进一步查找您自己的代码的堆栈跟踪。

您是正确的,不正确的 setState 调用可能会导致这种情况。例如,如果您在 componentDidUpdate 或 render 中未选中调用 setState。奇怪的是,它只是偶尔发生。

于 2017-03-29T04:11:18.510 回答