mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Use ES6 Map in ReactComponentTreeHook if available (#7491)
* Use ES6 Map in ReactComponentTreeHook if available * Make getRootIDs fast again * Only use native Map
This commit is contained in:
@@ -16,9 +16,46 @@ var ReactCurrentOwner = require('ReactCurrentOwner');
|
||||
var invariant = require('invariant');
|
||||
var warning = require('warning');
|
||||
|
||||
var itemByKey = {};
|
||||
var unmountedIDs = {};
|
||||
var rootIDs = {};
|
||||
function isNative(fn) {
|
||||
// Based on isNative() from Lodash
|
||||
var funcToString = Function.prototype.toString;
|
||||
var hasOwnProperty = Object.prototype.hasOwnProperty;
|
||||
var reIsNative = RegExp('^' + funcToString
|
||||
// Take an example native function source for comparison
|
||||
.call(hasOwnProperty)
|
||||
// Strip regex characters so we can use it for regex
|
||||
.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&')
|
||||
// Remove hasOwnProperty from the template to make it generic
|
||||
.replace(
|
||||
/hasOwnProperty|(function).*?(?=\\\()| for .+?(?=\\\])/g,
|
||||
'$1.*?'
|
||||
) + '$'
|
||||
);
|
||||
try {
|
||||
var source = funcToString.call(fn);
|
||||
return reIsNative.test(source);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
var itemMap;
|
||||
var itemByKey;
|
||||
|
||||
var canUseMap = (
|
||||
typeof Array.from === 'function' &&
|
||||
typeof Map === 'function' &&
|
||||
isNative(Map)
|
||||
);
|
||||
|
||||
if (canUseMap) {
|
||||
itemMap = new Map();
|
||||
} else {
|
||||
itemByKey = {};
|
||||
}
|
||||
|
||||
var unmountedIDs = [];
|
||||
var rootIDs = [];
|
||||
|
||||
// Use non-numeric keys to prevent V8 performance issues:
|
||||
// https://github.com/facebook/react/pull/7232
|
||||
@@ -30,18 +67,24 @@ function getIDFromKey(key) {
|
||||
}
|
||||
|
||||
function get(id) {
|
||||
if (canUseMap) {
|
||||
return itemMap.get(id);
|
||||
}
|
||||
var key = getKeyFromID(id);
|
||||
return itemByKey[key];
|
||||
}
|
||||
|
||||
function remove(id) {
|
||||
if (canUseMap) {
|
||||
itemMap.delete(id);
|
||||
return;
|
||||
}
|
||||
var key = getKeyFromID(id);
|
||||
delete itemByKey[key];
|
||||
}
|
||||
|
||||
function create(id, element, parentID) {
|
||||
var key = getKeyFromID(id);
|
||||
itemByKey[key] = {
|
||||
var item = {
|
||||
element,
|
||||
parentID,
|
||||
text: null,
|
||||
@@ -49,6 +92,12 @@ function create(id, element, parentID) {
|
||||
isMounted: false,
|
||||
updateCount: 0,
|
||||
};
|
||||
if (canUseMap) {
|
||||
itemMap.set(id, item);
|
||||
return;
|
||||
}
|
||||
var key = getKeyFromID(id);
|
||||
itemByKey[key] = item;
|
||||
}
|
||||
|
||||
function purgeDeep(id) {
|
||||
@@ -144,10 +193,6 @@ var ReactComponentTreeHook = {
|
||||
|
||||
onBeforeMountComponent(id, element, parentID) {
|
||||
create(id, element, parentID);
|
||||
|
||||
if (parentID === 0) {
|
||||
rootIDs[id] = true;
|
||||
}
|
||||
},
|
||||
|
||||
onBeforeUpdateComponent(id, element) {
|
||||
@@ -163,6 +208,9 @@ var ReactComponentTreeHook = {
|
||||
onMountComponent(id) {
|
||||
var item = get(id);
|
||||
item.isMounted = true;
|
||||
if (item.parentID === 0) {
|
||||
rootIDs.push(id);
|
||||
}
|
||||
},
|
||||
|
||||
onUpdateComponent(id) {
|
||||
@@ -184,9 +232,14 @@ var ReactComponentTreeHook = {
|
||||
// got a chance to mount, but it still gets an unmounting event during
|
||||
// the error boundary cleanup.
|
||||
item.isMounted = false;
|
||||
if (item.parentID === 0) {
|
||||
var indexInRootIDs = rootIDs.indexOf(id);
|
||||
if (indexInRootIDs !== -1) {
|
||||
rootIDs.splice(indexInRootIDs, 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
unmountedIDs[id] = true;
|
||||
delete rootIDs[id];
|
||||
unmountedIDs.push(id);
|
||||
},
|
||||
|
||||
purgeUnmountedComponents() {
|
||||
@@ -195,10 +248,11 @@ var ReactComponentTreeHook = {
|
||||
return;
|
||||
}
|
||||
|
||||
for (var id in unmountedIDs) {
|
||||
for (var i = 0; i < unmountedIDs.length; i++) {
|
||||
var id = unmountedIDs[i];
|
||||
purgeDeep(id);
|
||||
}
|
||||
unmountedIDs = {};
|
||||
unmountedIDs.length = 0;
|
||||
},
|
||||
|
||||
isMounted(id) {
|
||||
@@ -292,10 +346,13 @@ var ReactComponentTreeHook = {
|
||||
},
|
||||
|
||||
getRootIDs() {
|
||||
return Object.keys(rootIDs);
|
||||
return rootIDs;
|
||||
},
|
||||
|
||||
getRegisteredIDs() {
|
||||
if (canUseMap) {
|
||||
return Array.from(itemMap.keys());
|
||||
}
|
||||
return Object.keys(itemByKey).map(getIDFromKey);
|
||||
},
|
||||
};
|
||||
|
||||
@@ -46,14 +46,19 @@ describe('ReactComponentTreeHook', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function expectWrapperTreeToEqual(expectedTree) {
|
||||
function expectWrapperTreeToEqual(expectedTree, andStayMounted) {
|
||||
ReactComponentTreeTestUtils.expectTree(rootInstance._debugID, {
|
||||
displayName: 'Wrapper',
|
||||
children: expectedTree ? [expectedTree] : [],
|
||||
});
|
||||
var rootDisplayNames = ReactComponentTreeTestUtils.getRootDisplayNames();
|
||||
var registeredDisplayNames = ReactComponentTreeTestUtils.getRegisteredDisplayNames();
|
||||
if (!expectedTree) {
|
||||
expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
|
||||
expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
|
||||
expect(rootDisplayNames).toEqual([]);
|
||||
expect(registeredDisplayNames).toEqual([]);
|
||||
} else if (andStayMounted) {
|
||||
expect(rootDisplayNames).toContain('Wrapper');
|
||||
expect(registeredDisplayNames).toContain('Wrapper');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -64,12 +69,12 @@ describe('ReactComponentTreeHook', () => {
|
||||
|
||||
// Mount a new tree or update the existing tree.
|
||||
ReactDOM.render(<Wrapper />, node);
|
||||
expectWrapperTreeToEqual(expectedTree);
|
||||
expectWrapperTreeToEqual(expectedTree, true);
|
||||
|
||||
// Purging should have no effect
|
||||
// on the tree we expect to see.
|
||||
ReactComponentTreeHook.purgeUnmountedComponents();
|
||||
expectWrapperTreeToEqual(expectedTree);
|
||||
expectWrapperTreeToEqual(expectedTree, true);
|
||||
});
|
||||
|
||||
// Unmounting the root node should purge
|
||||
@@ -1864,4 +1869,107 @@ describe('ReactComponentTreeHook', () => {
|
||||
ReactDOM.render(<Foo />, el);
|
||||
});
|
||||
});
|
||||
|
||||
describe('in environment without Map and Array.from', () => {
|
||||
var realMap;
|
||||
var realArrayFrom;
|
||||
|
||||
beforeEach(() => {
|
||||
realMap = global.Map;
|
||||
realArrayFrom = Array.from;
|
||||
|
||||
global.Map = undefined;
|
||||
Array.from = undefined;
|
||||
|
||||
jest.resetModuleRegistry();
|
||||
|
||||
React = require('React');
|
||||
ReactDOM = require('ReactDOM');
|
||||
ReactDOMServer = require('ReactDOMServer');
|
||||
ReactInstanceMap = require('ReactInstanceMap');
|
||||
ReactComponentTreeHook = require('ReactComponentTreeHook');
|
||||
ReactComponentTreeTestUtils = require('ReactComponentTreeTestUtils');
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
global.Map = realMap;
|
||||
Array.from = realArrayFrom;
|
||||
});
|
||||
|
||||
it('works', () => {
|
||||
class Qux extends React.Component {
|
||||
render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function Foo() {
|
||||
return {
|
||||
render() {
|
||||
return <Qux />;
|
||||
},
|
||||
};
|
||||
}
|
||||
function Bar({children}) {
|
||||
return <h1>{children}</h1>;
|
||||
}
|
||||
class Baz extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<div>
|
||||
<Foo />
|
||||
<Bar>
|
||||
<span>Hi,</span>
|
||||
Mom
|
||||
</Bar>
|
||||
<a href="#">Click me.</a>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
var element = <Baz />;
|
||||
var tree = {
|
||||
displayName: 'Baz',
|
||||
element,
|
||||
children: [{
|
||||
displayName: 'div',
|
||||
children: [{
|
||||
displayName: 'Foo',
|
||||
element: <Foo />,
|
||||
children: [{
|
||||
displayName: 'Qux',
|
||||
element: <Qux />,
|
||||
children: [],
|
||||
}],
|
||||
}, {
|
||||
displayName: 'Bar',
|
||||
children: [{
|
||||
displayName: 'h1',
|
||||
children: [{
|
||||
displayName: 'span',
|
||||
children: [{
|
||||
displayName: '#text',
|
||||
element: 'Hi,',
|
||||
text: 'Hi,',
|
||||
}],
|
||||
}, {
|
||||
displayName: '#text',
|
||||
text: 'Mom',
|
||||
element: 'Mom',
|
||||
}],
|
||||
}],
|
||||
}, {
|
||||
displayName: 'a',
|
||||
children: [{
|
||||
displayName: '#text',
|
||||
text: 'Click me.',
|
||||
element: 'Click me.',
|
||||
}],
|
||||
}],
|
||||
}],
|
||||
};
|
||||
assertTreeMatches([element, tree]);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@@ -70,14 +70,19 @@ describe('ReactComponentTreeHook', () => {
|
||||
}
|
||||
}
|
||||
|
||||
function expectWrapperTreeToEqual(expectedTree) {
|
||||
function expectWrapperTreeToEqual(expectedTree, andStayMounted) {
|
||||
ReactComponentTreeTestUtils.expectTree(rootInstance._debugID, {
|
||||
displayName: 'Wrapper',
|
||||
children: expectedTree ? [expectedTree] : [],
|
||||
});
|
||||
var rootDisplayNames = ReactComponentTreeTestUtils.getRootDisplayNames();
|
||||
var registeredDisplayNames = ReactComponentTreeTestUtils.getRegisteredDisplayNames();
|
||||
if (!expectedTree) {
|
||||
expect(ReactComponentTreeTestUtils.getRootDisplayNames()).toEqual([]);
|
||||
expect(ReactComponentTreeTestUtils.getRegisteredDisplayNames()).toEqual([]);
|
||||
expect(rootDisplayNames).toEqual([]);
|
||||
expect(registeredDisplayNames).toEqual([]);
|
||||
} else if (andStayMounted) {
|
||||
expect(rootDisplayNames).toContain('Wrapper');
|
||||
expect(registeredDisplayNames).toContain('Wrapper');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -88,12 +93,12 @@ describe('ReactComponentTreeHook', () => {
|
||||
|
||||
// Mount a new tree or update the existing tree.
|
||||
ReactNative.render(<Wrapper />, 1);
|
||||
expectWrapperTreeToEqual(expectedTree);
|
||||
expectWrapperTreeToEqual(expectedTree, true);
|
||||
|
||||
// Purging should have no effect
|
||||
// on the tree we expect to see.
|
||||
ReactComponentTreeHook.purgeUnmountedComponents();
|
||||
expectWrapperTreeToEqual(expectedTree);
|
||||
expectWrapperTreeToEqual(expectedTree, true);
|
||||
});
|
||||
|
||||
// Unmounting the root node should purge
|
||||
|
||||
Reference in New Issue
Block a user