Helper for escaping and unescpaing component keys (#6500)

- Abstract escaping
- Provide human readible same key warnings
(cherry picked from commit dc6fc8cc07)
This commit is contained in:
hkal
2016-04-27 23:12:19 -07:00
committed by Paul O’Shannessy
parent afba0f3da7
commit da6e4853be
5 changed files with 109 additions and 39 deletions
@@ -14,6 +14,7 @@
var ReactReconciler = require('ReactReconciler');
var instantiateReactComponent = require('instantiateReactComponent');
var KeyEscapeUtils = require('KeyEscapeUtils');
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
var traverseAllChildren = require('traverseAllChildren');
var warning = require('warning');
@@ -27,7 +28,7 @@ function instantiateChild(childInstances, child, name) {
'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.',
name
KeyEscapeUtils.unescape(name)
);
}
if (child != null && keyUnique) {
+64
View File
@@ -0,0 +1,64 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule KeyEscapeUtils
*/
'use strict';
/**
* Escape and wrap key so it is safe to use as a reactid
*
* @param {*} key to be escaped.
* @return {string} the escaped key.
*/
function escape(key) {
var escapeRegex = /[=:]/g;
var escaperLookup = {
'=': '=0',
':': '=2',
};
var escapedString = ('' + key).replace(
escapeRegex,
function(match) {
return escaperLookup[match];
}
);
return '$' + escapedString;
}
/**
* 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];
}
);
}
var KeyEscapeUtils = {
escape: escape,
unescape: unescape,
};
module.exports = KeyEscapeUtils;
@@ -0,0 +1,38 @@
/**
* Copyright 2013-present, Facebook, Inc.
* All rights reserved.
*
* This source code is licensed under the BSD-style license found in the
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @emails react-core
*/
'use strict';
var KeyEscapeUtils;
describe('KeyEscapeUtils', () => {
beforeEach(() => {
jest.resetModuleRegistry();
KeyEscapeUtils = require('KeyEscapeUtils');
});
describe('escape', () => {
it('should properly escape and wrap user defined keys', () => {
expect(KeyEscapeUtils.escape('1')).toBe('$1');
expect(KeyEscapeUtils.escape('1=::=2')).toBe('$1=0=2=2=02');
});
});
describe('unescape', () => {
it('should properly unescape and unwrap user defined keys', () => {
expect(KeyEscapeUtils.unescape('.1')).toBe('1');
expect(KeyEscapeUtils.unescape('$1')).toBe('1');
expect(KeyEscapeUtils.unescape('.$1')).toBe('1');
expect(KeyEscapeUtils.unescape('$1=0=2=2=02')).toBe('1=::=2');
});
});
});
+2 -1
View File
@@ -11,6 +11,7 @@
'use strict';
var KeyEscapeUtils = require('KeyEscapeUtils');
var traverseAllChildren = require('traverseAllChildren');
var warning = require('warning');
@@ -29,7 +30,7 @@ function flattenSingleChildIntoContext(traverseContext, child, name) {
'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.',
name
KeyEscapeUtils.unescape(name)
);
}
if (keyUnique && child != null) {
+3 -37
View File
@@ -16,6 +16,7 @@ var ReactElement = require('ReactElement');
var getIteratorFn = require('getIteratorFn');
var invariant = require('invariant');
var KeyEscapeUtils = require('KeyEscapeUtils');
var warning = require('warning');
var SEPARATOR = '.';
@@ -26,19 +27,8 @@ var SUBSEPARATOR = ':';
* pattern.
*/
var userProvidedKeyEscaperLookup = {
'=': '=0',
':': '=2',
};
var userProvidedKeyEscapeRegex = /[=:]/g;
var didWarnAboutMaps = false;
function userProvidedKeyEscaper(match) {
return userProvidedKeyEscaperLookup[match];
}
/**
* Generate a key string that identifies a component within a set.
*
@@ -51,36 +41,12 @@ function getComponentKey(component, index) {
// that we don't block potential future ES APIs.
if (component && typeof component === 'object' && component.key != null) {
// Explicit key
return wrapUserProvidedKey(component.key);
return KeyEscapeUtils.escape(component.key);
}
// Implicit key determined by the index in the set
return index.toString(36);
}
/**
* Escape a component key so that it is safe to use in a reactid.
*
* @param {*} text Component key to be escaped.
* @return {string} An escaped string.
*/
function escapeUserProvidedKey(text) {
return ('' + text).replace(
userProvidedKeyEscapeRegex,
userProvidedKeyEscaper
);
}
/**
* Wrap a `key` value explicitly provided by the user to distinguish it from
* implicitly-generated keys generated by a component's index in its parent.
*
* @param {string} key Value of a user-provided `key` attribute
* @return {string}
*/
function wrapUserProvidedKey(key) {
return '$' + escapeUserProvidedKey(key);
}
/**
* @param {?*} children Children tree container.
* @param {!string} nameSoFar Name of the key path so far.
@@ -166,7 +132,7 @@ function traverseAllChildrenImpl(
child = entry[1];
nextName = (
nextNamePrefix +
wrapUserProvidedKey(entry[0]) + SUBSEPARATOR +
KeyEscapeUtils.escape(entry[0]) + SUBSEPARATOR +
getComponentKey(child, 0)
);
subtreeCount += traverseAllChildrenImpl(