Helper for escaping and unescpaing component keys (#6500)
- Abstract escaping - Provide human readible same key warnings
hkal committed
Apr 15, 2016 at 23:55 UTC
dc6fc8cc0726458a14f0544a30514af208d0098b
5 files changed
+109
-39
src/renderers/shared/reconciler/ReactChildReconciler.js
+2
-1
@@ -14,6 +14,7 @@
14
var ReactReconciler = require('ReactReconciler');
15
16
var instantiateReactComponent = require('instantiateReactComponent');
17
+var KeyEscapeUtils = require('KeyEscapeUtils');
18
var shouldUpdateReactComponent = require('shouldUpdateReactComponent');
19
var traverseAllChildren = require('traverseAllChildren');
20
var warning = require('warning');
@@ -27,7 +28,7 @@ function instantiateChild(childInstances, child, name) {
28
'flattenChildren(...): Encountered two children with the same key, ' +
29
'`%s`. Child keys must be unique; when two children share a key, only ' +
30
'the first child will be used.',
30
- name
31
+ KeyEscapeUtils.unescape(name)
32
);
33
}
34
if (child != null && keyUnique) {
src/shared/utils/KeyEscapeUtils.js
new
+64
@@ -0,0 +1,64 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @providesModule KeyEscapeUtils
10
+ */
11
+
12
+'use strict';
13
+
14
+/**
15
+ * Escape and wrap key so it is safe to use as a reactid
16
+ *
17
+ * @param {*} key to be escaped.
18
+ * @return {string} the escaped key.
19
+ */
20
+function escape(key) {
21
+ var escapeRegex = /[=:]/g;
22
+ var escaperLookup = {
23
+ '=': '=0',
24
+ ':': '=2',
25
+ };
26
+ var escapedString = ('' + key).replace(
27
+ escapeRegex,
28
+ function(match) {
29
+ return escaperLookup[match];
30
+ }
31
+ );
32
+
33
+ return '$' + escapedString;
34
+}
35
+
36
+/**
37
+ * Unescape and unwrap key for human-readable display
38
+ *
39
+ * @param {string} key to unescape.
40
+ * @return {string} the unescaped key.
41
+ */
42
+function unescape(key) {
43
+ var unescapeRegex = /(=0|=2)/g;
44
+ var unescaperLookup = {
45
+ '=0': '=',
46
+ '=2': ':',
47
+ };
48
+ var keySubstring = (key[0] === '.' && key[1] === '$')
49
+ ? key.substring(2) : key.substring(1);
50
+
51
+ return ('' + keySubstring).replace(
52
+ unescapeRegex,
53
+ function(match) {
54
+ return unescaperLookup[match];
55
+ }
56
+ );
57
+}
58
+
59
+var KeyEscapeUtils = {
60
+ escape: escape,
61
+ unescape: unescape,
62
+};
63
+
64
+module.exports = KeyEscapeUtils;
src/shared/utils/__tests__/KeyEscapeUtils-test.js
new
+38
@@ -0,0 +1,38 @@
1
+/**
2
+ * Copyright 2013-present, Facebook, Inc.
3
+ * All rights reserved.
4
+ *
5
+ * This source code is licensed under the BSD-style license found in the
6
+ * LICENSE file in the root directory of this source tree. An additional grant
7
+ * of patent rights can be found in the PATENTS file in the same directory.
8
+ *
9
+ * @emails react-core
10
+ */
11
+
12
+'use strict';
13
+
14
+var KeyEscapeUtils;
15
+
16
+describe('KeyEscapeUtils', () => {
17
+ beforeEach(() => {
18
+ jest.resetModuleRegistry();
19
+
20
+ KeyEscapeUtils = require('KeyEscapeUtils');
21
+ });
22
+
23
+ describe('escape', () => {
24
+ it('should properly escape and wrap user defined keys', () => {
25
+ expect(KeyEscapeUtils.escape('1')).toBe('$1');
26
+ expect(KeyEscapeUtils.escape('1=::=2')).toBe('$1=0=2=2=02');
27
+ });
28
+ });
29
+
30
+ describe('unescape', () => {
31
+ it('should properly unescape and unwrap user defined keys', () => {
32
+ expect(KeyEscapeUtils.unescape('.1')).toBe('1');
33
+ expect(KeyEscapeUtils.unescape('$1')).toBe('1');
34
+ expect(KeyEscapeUtils.unescape('.$1')).toBe('1');
35
+ expect(KeyEscapeUtils.unescape('$1=0=2=2=02')).toBe('1=::=2');
36
+ });
37
+ });
38
+});
src/shared/utils/flattenChildren.js
+2
-1
@@ -11,6 +11,7 @@
11
12
'use strict';
13
14
+var KeyEscapeUtils = require('KeyEscapeUtils');
15
var traverseAllChildren = require('traverseAllChildren');
16
var warning = require('warning');
17
@@ -29,7 +30,7 @@ function flattenSingleChildIntoContext(traverseContext, child, name) {
30
'flattenChildren(...): Encountered two children with the same key, ' +
31
'`%s`. Child keys must be unique; when two children share a key, only ' +
32
'the first child will be used.',
32
- name
33
+ KeyEscapeUtils.unescape(name)
34
);
35
}
36
if (keyUnique && child != null) {
src/shared/utils/traverseAllChildren.js
+3
-37
@@ -16,6 +16,7 @@ var ReactElement = require('ReactElement');
16
17
var getIteratorFn = require('getIteratorFn');
18
var invariant = require('invariant');
19
+var KeyEscapeUtils = require('KeyEscapeUtils');
20
var warning = require('warning');
21
22
var SEPARATOR = '.';
@@ -26,19 +27,8 @@ var SUBSEPARATOR = ':';
27
* pattern.
28
*/
29
29
-var userProvidedKeyEscaperLookup = {
30
- '=': '=0',
31
- ':': '=2',
32
-};
33
-
34
-var userProvidedKeyEscapeRegex = /[=:]/g;
35
-
30
var didWarnAboutMaps = false;
31
38
-function userProvidedKeyEscaper(match) {
39
- return userProvidedKeyEscaperLookup[match];
40
-}
41
-
32
/**
33
* Generate a key string that identifies a component within a set.
34
*
@@ -51,36 +41,12 @@ function getComponentKey(component, index) {
41
// that we don't block potential future ES APIs.
42
if (component && typeof component === 'object' && component.key != null) {
43
// Explicit key
54
- return wrapUserProvidedKey(component.key);
44
+ return KeyEscapeUtils.escape(component.key);
45
}
46
// Implicit key determined by the index in the set
47
return index.toString(36);
48
}
49
60
-/**
61
- * Escape a component key so that it is safe to use in a reactid.
62
- *
63
- * @param {*} text Component key to be escaped.
64
- * @return {string} An escaped string.
65
- */
66
-function escapeUserProvidedKey(text) {
67
- return ('' + text).replace(
68
- userProvidedKeyEscapeRegex,
69
- userProvidedKeyEscaper
70
- );
71
-}
72
-
73
-/**
74
- * Wrap a `key` value explicitly provided by the user to distinguish it from
75
- * implicitly-generated keys generated by a component's index in its parent.
76
- *
77
- * @param {string} key Value of a user-provided `key` attribute
78
- * @return {string}
79
- */
80
-function wrapUserProvidedKey(key) {
81
- return '$' + escapeUserProvidedKey(key);
82
-}
83
-
50
/**
51
* @param {?*} children Children tree container.
52
* @param {!string} nameSoFar Name of the key path so far.
@@ -166,7 +132,7 @@ function traverseAllChildrenImpl(
132
child = entry[1];
133
nextName = (
134
nextNamePrefix +
169
- wrapUserProvidedKey(entry[0]) + SUBSEPARATOR +
135
+ KeyEscapeUtils.escape(entry[0]) + SUBSEPARATOR +
136
getComponentKey(child, 0)
137
);
138
subtreeCount += traverseAllChildrenImpl(