mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add pure-render-mixin transform
This commit is contained in:
@@ -22,6 +22,18 @@ calls.
|
||||
|
||||
* `react-codemod findDOMNode <file>`
|
||||
|
||||
`pure-render-mixin.js` removes `PureRenderMixin` and inlines
|
||||
`shouldComponentUpdate` so that the ES6 class transform can pick up the React
|
||||
component and turn it into an ES6 class. NOTE: This currently only works if you
|
||||
are using the master version (>0.13.1) of React as it is using
|
||||
`React.addons.shallowCompare`
|
||||
|
||||
* `react-codemod pure-render-mixin <file>`
|
||||
* If `--mixin-name=<name>` is specified it will look for the specified name
|
||||
instead of `PureRenderMixin`. Note that it is not possible to use a
|
||||
namespaced name for the mixin. `mixins: [React.addons.PureRenderMixin]` will
|
||||
not currently work.
|
||||
|
||||
### Recast Options
|
||||
|
||||
Options to [recast](https://github.com/benjamn/recast)'s printer can be provided
|
||||
|
||||
@@ -38,5 +38,17 @@ describe('Transform Tests', () => {
|
||||
test('findDOMNode', 'findDOMNode-test');
|
||||
});
|
||||
|
||||
it('transforms the "pure-render-mixin" tests correctly', () => {
|
||||
test('pure-render-mixin', 'pure-render-mixin-test');
|
||||
|
||||
test('pure-render-mixin', 'pure-render-mixin-test2');
|
||||
|
||||
test('pure-render-mixin', 'pure-render-mixin-test3');
|
||||
|
||||
test('pure-render-mixin', 'pure-render-mixin-test4', {
|
||||
'mixin-name': 'ReactComponentWithPureRenderMixin',
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
});
|
||||
|
||||
@@ -0,0 +1,45 @@
|
||||
var React = require('react/addons');
|
||||
|
||||
var PureRenderMixin = React.addons.PureRenderMixin;
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
var MyMixedComponent = React.createClass({
|
||||
mixins: [PureRenderMixin, SomeOtherMixin],
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
var MyFooComponent = React.createClass({
|
||||
mixins: [PureRenderMixin, SomeOtherMixin],
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
|
||||
foo: function() {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
var MyStupidComponent = React.createClass({
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
shouldComponentUpdate: function() {
|
||||
return !!'wtf is this doing here?';
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,55 @@
|
||||
var React = require('react/addons');
|
||||
|
||||
var PureRenderMixin = React.addons.PureRenderMixin;
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return React.addons.shallowCompare(this, nextProps, nextState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
var MyMixedComponent = React.createClass({
|
||||
mixins: [SomeOtherMixin],
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return React.addons.shallowCompare(this, nextProps, nextState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
var MyFooComponent = React.createClass({
|
||||
mixins: [SomeOtherMixin],
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
},
|
||||
|
||||
foo: function() {
|
||||
|
||||
},
|
||||
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return React.addons.shallowCompare(this, nextProps, nextState);
|
||||
}
|
||||
});
|
||||
|
||||
var MyStupidComponent = React.createClass({
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
shouldComponentUpdate: function() {
|
||||
return !!'wtf is this doing here?';
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,13 @@
|
||||
var React = require('react/addons');
|
||||
|
||||
var PureRenderMixin = React.addons.PureRenderMixin;
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,13 @@
|
||||
var React = require('react/addons');
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return React.addons.shallowCompare(this, nextProps, nextState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,14 @@
|
||||
var React = require('react/addons');
|
||||
|
||||
var Foo = 'Foo';
|
||||
var PureRenderMixin = React.addons.PureRenderMixin;
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
mixins: [PureRenderMixin],
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,15 @@
|
||||
var React = require('react/addons');
|
||||
|
||||
var Foo = 'Foo';
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return React.addons.shallowCompare(this, nextProps, nextState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,12 @@
|
||||
var React = require('React');
|
||||
var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin');
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
mixins: [ReactComponentWithPureRenderMixin],
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,13 @@
|
||||
var React = require('React');
|
||||
|
||||
var MyComponent = React.createClass({
|
||||
shouldComponentUpdate: function(nextProps, nextState) {
|
||||
return React.addons.shallowCompare(this, nextProps, nextState);
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div />;
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = MyComponent;
|
||||
@@ -0,0 +1,179 @@
|
||||
/*eslint-disable no-comma-dangle*/
|
||||
|
||||
'use strict';
|
||||
|
||||
function removePureRenderMixin(file, api, options) {
|
||||
const j = api.jscodeshift;
|
||||
|
||||
require('./utils/array-polyfills');
|
||||
const ReactUtils = require('./utils/ReactUtils')(j);
|
||||
|
||||
const printOptions = options.printOptions || {quote: 'single'};
|
||||
const root = j(file.source);
|
||||
|
||||
const PURE_RENDER_MIXIN = options['mixin-name'] || 'PureRenderMixin';
|
||||
const SHOULD_COMPONENT_UPDATE = 'shouldComponentUpdate';
|
||||
const NEXT_PROPS = 'nextProps';
|
||||
const NEXT_STATE = 'nextState';
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// shouldComponentUpdate
|
||||
const createShouldComponentUpdateFunction = () =>
|
||||
j.functionExpression(
|
||||
null,
|
||||
[j.identifier(NEXT_PROPS), j.identifier(NEXT_STATE)],
|
||||
j.blockStatement([
|
||||
j.returnStatement(
|
||||
j.callExpression(
|
||||
j.memberExpression(
|
||||
j.identifier('React'),
|
||||
j.memberExpression(
|
||||
j.identifier('addons'),
|
||||
j.identifier('shallowCompare'),
|
||||
false
|
||||
),
|
||||
false
|
||||
),
|
||||
[
|
||||
j.thisExpression(),
|
||||
j.identifier(NEXT_PROPS),
|
||||
j.identifier(NEXT_STATE)
|
||||
]
|
||||
)
|
||||
)
|
||||
])
|
||||
);
|
||||
|
||||
const createShouldComponentUpdateProperty = () =>
|
||||
j.property(
|
||||
'init',
|
||||
j.identifier(SHOULD_COMPONENT_UPDATE),
|
||||
createShouldComponentUpdateFunction()
|
||||
);
|
||||
|
||||
const hasShouldComponentUpdate = classPath =>
|
||||
ReactUtils.getReactCreateClassSpec(classPath)
|
||||
.properties.every(property =>
|
||||
property.key.name !== SHOULD_COMPONENT_UPDATE
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Mixin related code
|
||||
const isPureRenderMixin = node => (
|
||||
node.type === 'Identifier' &&
|
||||
node.name === PURE_RENDER_MIXIN
|
||||
);
|
||||
|
||||
const hasPureRenderMixin = classPath => {
|
||||
const spec = ReactUtils.getReactCreateClassSpec(classPath);
|
||||
const mixin = spec && spec.properties.find(ReactUtils.isMixinProperty);
|
||||
return mixin && mixin.value.elements.some(isPureRenderMixin);
|
||||
};
|
||||
|
||||
const removeMixin = elements =>
|
||||
j.property(
|
||||
'init',
|
||||
j.identifier('mixins'),
|
||||
j.arrayExpression(
|
||||
elements.filter(element => !isPureRenderMixin(element))
|
||||
)
|
||||
);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Boom!
|
||||
const insertShouldComponentUpdate = properties => {
|
||||
const length = properties.length;
|
||||
const lastProp = properties[length - 1];
|
||||
// I wouldn't dare insert at the bottom if the last function is render
|
||||
if (
|
||||
lastProp.key.type === 'Identifier' &&
|
||||
lastProp.key.name === 'render'
|
||||
) {
|
||||
properties.splice(
|
||||
length - 1,
|
||||
1,
|
||||
createShouldComponentUpdateProperty(),
|
||||
lastProp
|
||||
);
|
||||
} else {
|
||||
properties.push(createShouldComponentUpdateProperty());
|
||||
}
|
||||
return properties;
|
||||
};
|
||||
|
||||
const cleanupReactComponent = classPath => {
|
||||
const spec = ReactUtils.getReactCreateClassSpec(classPath);
|
||||
const properties = spec.properties
|
||||
.map(property => {
|
||||
if (ReactUtils.isMixinProperty(property)) {
|
||||
const elements = property.value.elements;
|
||||
return (elements.length !== 1) ? removeMixin(elements) : null;
|
||||
}
|
||||
return property;
|
||||
})
|
||||
.filter(property => !!property);
|
||||
|
||||
ReactUtils.findReactCreateClassCallExpression(classPath).replaceWith(
|
||||
ReactUtils.createCreateReactClassCallExpression(
|
||||
insertShouldComponentUpdate(properties)
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
// Remove it if only two or fewer are left:
|
||||
// var PureRenderMixin = React.addons.PureRenderMixin;
|
||||
const hasPureRenderIdentifiers = path =>
|
||||
path.find(j.Identifier, {
|
||||
name: PURE_RENDER_MIXIN,
|
||||
}).size() > 2;
|
||||
|
||||
const deletePureRenderMixin = path => {
|
||||
if (hasPureRenderIdentifiers(path)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const declaration = path
|
||||
.findVariableDeclarators(PURE_RENDER_MIXIN)
|
||||
.closest(j.VariableDeclaration);
|
||||
|
||||
if (declaration.size > 1) {
|
||||
declaration.forEach(p =>
|
||||
j(p).replaceWith(
|
||||
j.variableDeclaration(
|
||||
'var',
|
||||
p.value.declarations.filter(isPureRenderMixin)
|
||||
)
|
||||
)
|
||||
);
|
||||
} else {
|
||||
// Let's assume the variable declaration happens at the top level
|
||||
const program = declaration.closest(j.Program).get();
|
||||
const body = program.value.body;
|
||||
const index = body.indexOf(declaration.get().value);
|
||||
if (index !== -1) {
|
||||
body.splice(index, 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (
|
||||
options['no-explicit-require'] ||
|
||||
ReactUtils.hasReact(root)
|
||||
) {
|
||||
const didTransform = ReactUtils
|
||||
.findReactCreateClass(root)
|
||||
.filter(hasPureRenderMixin)
|
||||
.filter(hasShouldComponentUpdate)
|
||||
.forEach(cleanupReactComponent)
|
||||
.size() > 0;
|
||||
|
||||
if (didTransform) {
|
||||
deletePureRenderMixin(root);
|
||||
return root.toSource(printOptions) + '\n';
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
module.exports = removePureRenderMixin;
|
||||
Reference in New Issue
Block a user