【问题标题】:React.cloneElement inside React.Children.map is causing element keys to changeReact.Children.map 中的 React.cloneElement 导致元素键发生变化
【发布时间】:2018-04-12 05:14:14
【问题描述】:

正如标题所述,在 React.Children.map 内使用 React.cloneElement 会导致元素键发生变化。

这里有一个sandbox 演示了这一点。

React.Children.map(children, (child) => {
    let clonedEl = React.cloneElement( child );
    console.log(clonedEl);
    return clonedEl;
});

该代码块的结果包含带有.$ 的元素添加到每个键的前面。这确实令人困惑,原因有两个。

1:文档说cloneElement 将保留键和引用。

使用 element 作为起点克隆并返回一个新的 React 元素。结果元素将具有原始元素的道具和新道具的浅层合并。新的孩子将取代现有的孩子。原始元素的 key 和 ref 将被保留。

2:console.log 的结果是一个带有保留键和引用的元素...

这会让我相信添加发生在 React.Children.map 代码的某个地方。

更新:查看 React.Children.map 的代码后...

我发现它是由以下函数链添加的:mapChilren -> mapIntoWithKeyPrefixInternal -> traverseAllChildren -> traverseAllChildrenImpl -> mapSingleChildIntoContext。

mapSingleChildIntoContext 的第三个参数是 childKey。它是用nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar 调用的,因为它是traverseAllChildrenImpl 中的第三个参数。

SEPARATOR = "."getComponentKey 在转义函数中返回带有 $ 前缀的键。

更新问题:

现在我正在寻找解决此问题的方法...我不确定是否有人考虑使用空字符串调用 traverseAllChildrenImpl 作为 traverseAllChildren 中的 nameSoFar

我认为这可能是 React.Children.map 构建新 DOM 的预期行为。当我尝试更新动态孩子的道具时,这对我造成了影响。

解决方案:不要使用不打算使用的东西。

我正在构建一组对开发人员来说非常容易的表单控件。状态树是通过映射子节点并使用 .从具有名称的元素中划定字符串名称,以在顶级组件上创建键和值。

顶级表单组件具有用于不同类型控件的 onChange 处理程序,它们根据需要应用于元素的 onChange 属性。此映射是在 componentWillMount 方法中完成的,这就是导致我出现问题的原因。

将映射移动到渲染方法使我不必更新句柄中的子级。句柄中的更新导致元素失去焦点。现在一切都很好!

【问题讨论】:

  • 不完全是您问题的答案,但您可以使用 React.Children.forEach 作为替代方案。
  • 好点,我可以用forEach“手动”重建孩子。我认为这可能必须解决。我认为这可能是地图的预期行为。

标签: javascript reactjs


【解决方案1】:

问题不在于更改密钥的cloneElement。正如文档中所写,cloneElement 保留了原始键。它是为它添加前缀的React.Children.map。如果您不想更改密钥,请使用forEach 而不是map

这是React Code的摘录:

function escape(key) {
  var escapeRegex = /[=:]/g;
  var escaperLookup = {
    '=': '=0',
    ':': '=2',
  };
  var escapedString = ('' + key).replace(escapeRegex, function(match) {
    return escaperLookup[match];
  });

  return '$' + escapedString;
}

function getComponentKey(component, index) {
  // Do some typechecking here since we call this blindly. We want to ensure
  // that we don't block potential future ES APIs.
  if (
    typeof component === 'object' &&
    component !== null &&
    component.key != null
  ) {
    // Explicit key
    return escape(component.key);
  }
  // Implicit key determined by the index in the set
  return index.toString(36);
}

function mapSingleChildIntoContext(bookKeeping, child, childKey) {
  var {result, keyPrefix, func, context} = bookKeeping;

  var mappedChild = func.call(context, child, bookKeeping.count++);
  if (Array.isArray(mappedChild)) {
    mapIntoWithKeyPrefixInternal(
      mappedChild,
      result,
      childKey,
      emptyFunction.thatReturnsArgument,
    );
  } else if (mappedChild != null) {
    if (ReactElement.isValidElement(mappedChild)) {
      mappedChild = ReactElement.cloneAndReplaceKey(
        mappedChild,
        // Keep both the (mapped) and old keys if they differ, just as
        // traverseAllChildren used to do for objects as children
        keyPrefix +
          (mappedChild.key && (!child || child.key !== mappedChild.key)
            ? escapeUserProvidedKey(mappedChild.key) + '/'
            : '') +
          childKey,
      );
    }
    result.push(mappedChild);
  }
}

function mapChildren(children, func, context) {
  if (children == null) {
    return children;
  }
  var result = [];
  mapIntoWithKeyPrefixInternal(children, result, null, func, context);
  return result;
}

【讨论】:

  • 欣赏答案,但我已经解决了这一点:) 甚至提到了那些确切的功能!考虑到您花费的时间,无论如何都会给您功劳!
  • 几个小时后才发现使用React.Children.toArray时密钥也被修改了。
  • 谢谢你。令我困扰的是,他们没有在官方文件中警告您可能会发生这种情况。对于 React.Children.toArray,您会收到警告,但对于 React.Children.map 则不会。花了很长时间试图理解为什么不必要地重新安装东西只是为了发现这一点。
  • @Jordy 嗯,是的,这是你在真正深入时发现的东西。然而,重新安装而不是重新渲染几乎总是与键相关联
  • @Shubham Khatri 你可能是对的。我目前正在尝试做一些奇怪的事情,包括为过去的孩子提供参考,以创建我自己的延迟卸载系统。因此,发生不需要的重新安装很可能是我的错:)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2010-09-30
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多