Set up style mutation monitoring

This works by storing the style and its copy onto the component, and do
the appropriate comparison during `_updateDOMProperties`.
This commit is contained in:
Cheng Lou
2015-03-16 03:56:54 -04:00
parent 3fad007bdb
commit 6e992fbbdd
2 changed files with 97 additions and 0 deletions
+52
View File
@@ -29,6 +29,7 @@ var escapeTextContentForBrowser = require('escapeTextContentForBrowser');
var invariant = require('invariant');
var isEventSupported = require('isEventSupported');
var keyOf = require('keyOf');
var shallowEqual = require('shallowEqual');
var validateDOMNesting = require('validateDOMNesting');
var warning = require('warning');
@@ -43,6 +44,44 @@ var STYLE = keyOf({style: null});
var ELEMENT_NODE_TYPE = 1;
var styleMutationWarning = {};
function checkAndWarnForMutatedStyle(style1, style2, component) {
if (style1 == null || style2 == null) {
return;
}
if (shallowEqual(style1, style2)) {
return;
}
var componentName = component._tag;
var owner = component._currentElement._owner;
var ownerName;
if (owner) {
ownerName = owner.getName();
}
var hash = ownerName + '|' + componentName;
if (styleMutationWarning.hasOwnProperty(hash)) {
return;
}
styleMutationWarning[hash] = true;
warning(
false,
'`%s` was passed a style object that has previously been mutated. ' +
'Mutating `style` will be deprecated. Consider cloning it beforehand. ' +
'Check the `render` %s. Previous style: %s. Mutated style: %s.',
componentName,
owner ? 'of `' + ownerName + '`' : 'using <' + componentName + '>',
JSON.stringify(style1),
JSON.stringify(style2)
);
}
/**
* Optionally injectable operations for mutating the DOM
*/
@@ -201,6 +240,7 @@ function ReactDOMComponent(tag) {
validateDangerousTag(tag);
this._tag = tag;
this._renderedChildren = null;
this._previousStyle = null;
this._previousStyleCopy = null;
this._rootNodeID = null;
}
@@ -274,6 +314,10 @@ ReactDOMComponent.Mixin = {
} else {
if (propKey === STYLE) {
if (propValue) {
if (__DEV__) {
// See `_updateDOMProperties`. style block
this._previousStyle = propValue;
}
propValue = this._previousStyleCopy = assign({}, props.style);
}
propValue = CSSPropertyOperations.createMarkupForStyles(propValue);
@@ -441,6 +485,14 @@ ReactDOMComponent.Mixin = {
}
if (propKey === STYLE) {
if (nextProp) {
if (__DEV__) {
checkAndWarnForMutatedStyle(
this._previousStyleCopy,
this._previousStyle,
this
);
this._previousStyle = nextProp;
}
nextProp = this._previousStyleCopy = assign({}, nextProp);
}
if (lastProp) {
@@ -60,7 +60,11 @@ describe('ReactDOMComponent', function() {
expect(stubStyle.fontFamily).toEqual('');
});
// TODO: (poshannessy) deprecate this pattern.
it("should update styles when mutating style object", function() {
// not actually used. Just to suppress the style mutation warning
spyOn(console, 'error');
var styles = {display: 'none', fontFamily: 'Arial', lineHeight: 1.2};
var container = document.createElement('div');
React.render(<div style={styles} />, container);
@@ -96,6 +100,46 @@ describe('ReactDOMComponent', function() {
expect(stubStyle.lineHeight).toBe('');
});
it('should warn when mutating style', function() {
spyOn(console, 'error');
var style = {border: '1px solid black'};
var App = React.createClass({
getInitialState: function() {
return {style: style};
},
render: function() {
return <div style={this.state.style}>asd</div>;
}
});
var stub = ReactTestUtils.renderIntoDocument(<App />);
style.position = 'absolute';
stub.setState({style: style});
expect(console.error.argsForCall.length).toBe(1);
expect(console.error.argsForCall[0][0]).toEqual(
'Warning: `div` was passed a style object that has previously been ' +
'mutated. Mutating `style` will be deprecated. Consider cloning it ' +
'beforehand. Check the `render` of `App`. Previous style: ' +
'{"border":"1px solid black"}. Mutated style: ' +
'{"border":"1px solid black","position":"absolute"}.'
);
style = {background: 'red'};
stub = ReactTestUtils.renderIntoDocument(<App />);
style.background = 'green';
stub.setState({style: {background: 'green'}});
// already warned once for the same component and owner
expect(console.error.argsForCall.length).toBe(1);
style = {background: 'red'};
var div = document.createElement('div');
React.render(<span style={style}></span>, div);
style.background = 'blue';
React.render(<span style={style}></span>, div);
expect(console.error.argsForCall.length).toBe(2);
});
it("should update styles if initially null", function() {
var styles = null;
var container = document.createElement('div');
@@ -152,6 +196,7 @@ describe('ReactDOMComponent', function() {
expect(stubStyle.color).toEqual('');
});
// trouble
it("should update styles when 'style' changes from null to object", function() {
var container = document.createElement('div');
var styles = {color: 'red'};