React.Children.map中的React.cloneElement导致元素键改变

时间:2017-10-31 05:41:12

标签: javascript reactjs

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

这是一个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的结果是一个保留键和ref ...的元素

这会让我相信在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方法中完成的,这也是导致我出现问题的原因。

将映射移动到render方法允许我不必更新句柄中的子项。句柄中的更新导致元素失去焦点。一切都很好!

1 个答案:

答案 0 :(得分:6)

问题不在于更改密钥的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;
}