如标题所述,使用React.cloneElementinside React.Children.map导致元素键发生更改。
React.cloneElement
React.Children.map
这是一个演示此内容的沙箱。
React.Children.map(children, (child) => { let clonedEl = React.cloneElement( child ); console.log(clonedEl); return clonedEl; });
该代码块的结果将元素.$添加到每个键的前面。这确实令人困惑,原因有两个。
.$
1:文档说cloneElement将保留键和引用。
克隆并使用element作为起点返回一个新的React元素。生成的元素将具有原始元素的道具,而新的道具将被浅层合并。新的孩子将替换现有的孩子。原始元素的key和ref将被保留。
2:的结果console.log是具有保留键和引用的元素。
console.log
这会让我相信添加操作发生在React.Children.map代码的某个地方。
更新:查看React.Children.map的代码后…
我发现它是通过以下功能链添加的:mapChilren-> mapIntoWithKeyPrefixInternal-> traverseAllChildren-> traverseAllChildrenImpl-> mapSingleChildIntoContext。
mapSingleChildIntoContext第三个参数是childKey。它被称为with nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar作为它的第三个参数traverseAllChildrenImpl。
mapSingleChildIntoContext
nameSoFar === '' ? SEPARATOR + getComponentKey(children, 0) : nameSoFar
traverseAllChildrenImpl
SEPARATOR = "."并getComponentKey在转义函数中返回带有$前缀的键。
SEPARATOR = "."
getComponentKey
更新的问题:
现在,我正在寻找解决此问题的方法…我不确定是否有考虑将traverseAllChildrenImpl调用为空字符串作为nameSoFartraverseAllChildren内部对象的问题。
nameSoFar
我认为这可能是React.Children.map构建新DOM 的预期行为。这在尝试更新动态子项上的道具时给我造成了困扰。
解决方案:不要使用不打算使用的东西。
我正在构建一组窗体控件,这些控件对于开发人员来说真的很容易。状态树是通过映射子级并使用来动态构建的。从具有名称的元素中划定字符串名称,以在顶级组件上创建键和值。
顶层表单组件具有用于不同类型控件的onChange处理程序,并且根据需要将其应用于元素的onChange属性。这种映射是在componentWillMount方法中完成的,这就是导致我遇到问题的原因。
将映射移动到render方法可以使我不必更新句柄中的子级。手柄中的更新导致元素失去焦点。现在一切都好!
问题不cloneElement在于改变您的钥匙。如文档中所述,cloneElement保留原始密钥。它React.Children.map为其添加了前缀。如果您不希望更改键,请使用forEach代替map
cloneElement
forEach
map
这是React代码的摘录:
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; }