Skip to content

Commit 7d18235

Browse files
committed
Add ReactDataTracker to Addons
1 parent b687a22 commit 7d18235

File tree

6 files changed

+240
-1
lines changed

6 files changed

+240
-1
lines changed

src/addons/ReactWithAddons.js

+10-1
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ var React = require('React');
2323
var ReactComponentWithPureRenderMixin =
2424
require('ReactComponentWithPureRenderMixin');
2525
var ReactCSSTransitionGroup = require('ReactCSSTransitionGroup');
26+
var ReactDataTracker = require('ReactDataTracker');
2627
var ReactFragment = require('ReactFragment');
2728
var ReactTransitionGroup = require('ReactTransitionGroup');
2829
var ReactUpdates = require('ReactUpdates');
@@ -43,7 +44,15 @@ React.addons = {
4344
createFragment: ReactFragment.create,
4445
renderSubtreeIntoContainer: renderSubtreeIntoContainer,
4546
shallowCompare: shallowCompare,
46-
update: update
47+
update: update,
48+
observeRead: function(reactDataEntity) {
49+
ReactDataTracker.startRead(reactDataEntity);
50+
ReactDataTracker.endRead(reactDataEntity);
51+
},
52+
observeWrite: function(reactDataEntity) {
53+
ReactDataTracker.startWrite(reactDataEntity);
54+
ReactDataTracker.endWrite(reactDataEntity);
55+
}
4756
};
4857

4958
if (__DEV__) {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
/**
2+
* Copyright 2013-2015, 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 React = require('ReactWithAddons');
15+
16+
describe('ReactDataTrack', function() {
17+
18+
it('should update component when a write fires', function () {
19+
20+
class Person {
21+
constructor(name) {
22+
this.setName(name);
23+
}
24+
25+
setName(name) {
26+
this.name = name;
27+
React.addons.observeWrite(this);
28+
}
29+
30+
getName() {
31+
React.addons.observeRead(this);
32+
return this.name;
33+
}
34+
}
35+
36+
class PersonView extends React.Component {
37+
render() {
38+
return <div>{this.props.person.getName()}</div>;
39+
}
40+
}
41+
42+
var container = document.createElement('div');
43+
44+
var person = new Person("jimfb");
45+
React.render(<PersonView person={person} />, container);
46+
person.setName("Jim");
47+
expect(container.children[0].innerHTML).toBe('Jim');
48+
React.unmountComponentAtNode(container);
49+
});
50+
});

src/renderers/dom/client/ReactMount.js

+2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
var DOMProperty = require('DOMProperty');
1515
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
1616
var ReactCurrentOwner = require('ReactCurrentOwner');
17+
var ReactDataTracker = require('ReactDataTracker');
1718
var ReactElement = require('ReactElement');
1819
var ReactElementValidator = require('ReactElementValidator');
1920
var ReactEmptyComponent = require('ReactEmptyComponent');
@@ -698,6 +699,7 @@ var ReactMount = {
698699
);
699700
delete instancesByReactRootID[reactRootID];
700701
delete containersByReactRootID[reactRootID];
702+
ReactDataTracker.unmount(component);
701703
if (__DEV__) {
702704
delete rootElementsByReactRootID[reactRootID];
703705
}

src/renderers/shared/reconciler/ReactCompositeComponent.js

+3
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
var ReactComponentEnvironment = require('ReactComponentEnvironment');
1515
var ReactContext = require('ReactContext');
1616
var ReactCurrentOwner = require('ReactCurrentOwner');
17+
var ReactDataTracker = require('ReactDataTracker');
1718
var ReactElement = require('ReactElement');
1819
var ReactElementValidator = require('ReactElementValidator');
1920
var ReactInstanceMap = require('ReactInstanceMap');
@@ -739,7 +740,9 @@ var ReactCompositeComponentMixin = {
739740
*/
740741
_renderValidatedComponentWithoutOwnerOrContext: function() {
741742
var inst = this._instance;
743+
ReactDataTracker.startRender(inst);
742744
var renderedComponent = inst.render();
745+
ReactDataTracker.endRender(inst);
743746
if (__DEV__) {
744747
// We allow auto-mocks to proceed as if they're returning null.
745748
if (typeof renderedComponent === 'undefined' &&
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
/**
2+
* Copyright 2015, 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 ReactDataTracker
10+
*/
11+
'use strict';
12+
13+
// TODO: Using the ES6 Polyfill
14+
// Using expando properties might be a possibility, but I opted away from this because:
15+
// 1. This code isn't production-ready yet anyway, this code is mostly to demo purposes
16+
// 2. New browsers support ES6 maps, so this only has perf ramifications on legacy browsers
17+
// 3. Perhaps most importantly: The data entities are user data objects, meaning that
18+
// they could be frozen, or iterated over, or any number of other edge cases that
19+
// would make adding expando properties a fairly unfriendly thing to do.
20+
var Es6Map = (typeof Map !== 'undefined' ? Map : require('es6-collections').Map);
21+
22+
var ReactDataTracker = {
23+
startRender: function(component) {
24+
ReactDataTracker.currentContext = [];
25+
if (ReactDataTracker.listeners === undefined) {
26+
ReactDataTracker.listeners = new Es6Map();
27+
}
28+
if (ReactDataTracker.dataSources === undefined) {
29+
ReactDataTracker.dataSources = new Es6Map();
30+
}
31+
32+
if (!ReactDataTracker.dataSources.has(component)) {
33+
ReactDataTracker.dataSources.set(component, []);
34+
}
35+
},
36+
37+
endRender: function(component) {
38+
var oldDataSources = ReactDataTracker.dataSources.get(component);
39+
var newDataSources = ReactDataTracker.currentContext;
40+
var index = 0;
41+
42+
for (index = 0; index < oldDataSources.length; index++) {
43+
if (newDataSources.indexOf(oldDataSources[index]) === -1) {
44+
var oldListeners = ReactDataTracker.listeners.get(oldDataSources[index]);
45+
oldListeners.splice(oldListeners.indexOf(component), 1);
46+
oldDataSources.splice(index, 1);
47+
index--;
48+
}
49+
}
50+
for (index = 0; index < newDataSources.length; index++) {
51+
if (oldDataSources.indexOf(newDataSources[index]) === -1) {
52+
if (!ReactDataTracker.listeners.has(newDataSources[index])) {
53+
ReactDataTracker.listeners.set(newDataSources[index], []);
54+
}
55+
ReactDataTracker.listeners.get(newDataSources[index]).push(component);
56+
ReactDataTracker.dataSources.get(component).push(newDataSources[index]);
57+
}
58+
}
59+
},
60+
61+
startRead: function(entity) {
62+
if (ReactDataTracker.activeReaders === undefined) {
63+
ReactDataTracker.activeReaders = 0;
64+
}
65+
ReactDataTracker.activeReaders++;
66+
},
67+
68+
endRead: function(entity) {
69+
if (ReactDataTracker.currentContext !== undefined && ReactDataTracker.currentContext.indexOf(entity) === -1) {
70+
ReactDataTracker.currentContext.push(entity);
71+
}
72+
ReactDataTracker.activeReaders--;
73+
if (ReactDataTracker.activeReaders < 0) {
74+
throw new Error('Number of active readers dropped below zero');
75+
}
76+
},
77+
78+
startWrite: function(entity) {
79+
if (ReactDataTracker.writers === undefined) {
80+
ReactDataTracker.writers = [];
81+
}
82+
if (ReactDataTracker.writers.indexOf(entity) === -1) {
83+
ReactDataTracker.writers.push(entity);
84+
}
85+
if (ReactDataTracker.activeWriters === undefined) {
86+
ReactDataTracker.activeWriters = 0;
87+
}
88+
ReactDataTracker.activeWriters++;
89+
},
90+
91+
endWrite: function(entity) {
92+
if (ReactDataTracker.activeWriters === undefined) {
93+
throw new Error('Can not end write without starting write');
94+
}
95+
if (ReactDataTracker.writers.indexOf(entity) === -1) {
96+
throw new Error('Can not end write without starting write');
97+
}
98+
ReactDataTracker.activeWriters--;
99+
100+
if (ReactDataTracker.activeWriters === 0) {
101+
// for each writer that wrote during this batch
102+
var componentsToNotify = [];
103+
for (var writerIndex = 0; writerIndex < ReactDataTracker.writers.length; writerIndex++) {
104+
var writer = ReactDataTracker.writers[writerIndex];
105+
if (ReactDataTracker.listeners === undefined) {
106+
continue;
107+
}
108+
if (!ReactDataTracker.listeners.has(writer)) {
109+
continue;
110+
}
111+
var listenersList = ReactDataTracker.listeners.get(writer);
112+
for (var index = 0; index < listenersList.length; index++) {
113+
if (componentsToNotify.indexOf(listenersList[index]) === -1) {
114+
componentsToNotify.push(listenersList[index]);
115+
}
116+
}
117+
}
118+
119+
for (var componentIndex = 0; componentIndex < componentsToNotify.length; componentIndex++) {
120+
componentsToNotify[componentIndex].setState({});
121+
}
122+
ReactDataTracker.writers = [];
123+
}
124+
},
125+
126+
unmount: function(component) {
127+
var oldDataSources = ReactDataTracker.dataSources.get(component);
128+
if (oldDataSources === undefined) {
129+
return;
130+
}
131+
for (var index = 0; index < oldDataSources.length; index++) {
132+
var entityListeners = ReactDataTracker.listeners.get(oldDataSources[index]);
133+
var entityListenerPosition = entityListeners.indexOf(component);
134+
if (entityListenerPosition > -1) {
135+
entityListeners.splice(entityListeners.indexOf(component), 1);
136+
} else {
137+
throw new Error('Unable to find listener when unmounting component');
138+
}
139+
}
140+
ReactDataTracker.dataSources.delete(component);
141+
}
142+
};
143+
144+
module.exports = ReactDataTracker;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
/**
2+
*
3+
* Copyright (C) 2011 by Andrea Giammarchi, @WebReflection
4+
*
5+
* Permission is hereby granted, free of charge, to any person obtaining a copy
6+
* of this software and associated documentation files (the "Software"), to deal
7+
* in the Software without restriction, including without limitation the rights
8+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
* copies of the Software, and to permit persons to whom the Software is
10+
* furnished to do so, subject to the following conditions:
11+
*
12+
* The above copyright notice and this permission notice shall be included in
13+
* all copies or substantial portions of the Software.
14+
*
15+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
21+
* THE SOFTWARE.
22+
*
23+
* @providesModule es6-collections
24+
*/
25+
26+
(function(e){function f(a,c){function b(a){if(!this||this.constructor!==b)return new b(a);this._keys=[];this._values=[];this._itp=[];this.objectOnly=c;a&&v.call(this,a)}c||w(a,"size",{get:x});a.constructor=b;b.prototype=a;return b}function v(a){this.add?a.forEach(this.add,this):a.forEach(function(a){this.set(a[0],a[1])},this)}function d(a){this.has(a)&&(this._keys.splice(b,1),this._values.splice(b,1),this._itp.forEach(function(a){b<a[0]&&a[0]--}));return-1<b}function m(a){return this.has(a)?this._values[b]:
27+
void 0}function n(a,c){if(this.objectOnly&&c!==Object(c))throw new TypeError("Invalid value used as weak collection key");if(c!=c||0===c)for(b=a.length;b--&&!y(a[b],c););else b=a.indexOf(c);return-1<b}function p(a){return n.call(this,this._values,a)}function q(a){return n.call(this,this._keys,a)}function r(a,c){this.has(a)?this._values[b]=c:this._values[this._keys.push(a)-1]=c;return this}function t(a){this.has(a)||this._values.push(a);return this}function h(){this._values.length=0}function z(){return k(this._itp,
28+
this._keys)}function l(){return k(this._itp,this._values)}function A(){return k(this._itp,this._keys,this._values)}function B(){return k(this._itp,this._values,this._values)}function k(a,c,b){var g=[0],e=!1;a.push(g);return{next:function(){var f,d=g[0];!e&&d<c.length?(f=b?[c[d],b[d]]:c[d],g[0]++):(e=!0,a.splice(a.indexOf(g),1));return{done:e,value:f}}}}function x(){return this._values.length}function u(a,c){for(var b=this.entries();;){var d=b.next();if(d.done)break;a.call(c,d.value[1],d.value[0],
29+
this)}}var b,w=Object.defineProperty,y=function(a,b){return isNaN(a)?isNaN(b):a===b};"undefined"==typeof WeakMap&&(e.WeakMap=f({"delete":d,clear:h,get:m,has:q,set:r},!0));"undefined"==typeof Map&&(e.Map=f({"delete":d,has:q,get:m,set:r,keys:z,values:l,entries:A,forEach:u,clear:h}));"undefined"==typeof Set&&(e.Set=f({has:p,add:t,"delete":d,clear:h,keys:l,values:l,entries:B,forEach:u}));"undefined"==typeof WeakSet&&(e.WeakSet=f({"delete":d,add:t,clear:h,has:p},!0))})("undefined"!=typeof exports&&"undefined"!=
30+
typeof global?global:window);
31+

0 commit comments

Comments
 (0)