Skip to content

Commit 6d9e3cb

Browse files
author
Keyan Zhang
committed
added initial working version
1 parent 2c5b461 commit 6d9e3cb

File tree

7 files changed

+261
-165
lines changed

7 files changed

+261
-165
lines changed

README.md

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,6 +146,56 @@ The constructor logic is as follows:
146146
* Changes `return StateObject` from `getInitialState` to assign `this.state`
147147
directly.
148148

149+
### Explanation of the new ES2015 class transform with property initializers
150+
151+
* Ignore components with calls to deprecated APIs. This is very defensive, if
152+
the script finds any identifiers called `isMounted`, `getDOMNode`,
153+
`replaceProps`, `replaceState` or `setProps` it will skip the component.
154+
* Replaces `var A = React.createClass(spec)` with
155+
`class A (extends React.Component) {spec}`.
156+
* Pulls out all statics defined on `statics` plus the few special cased
157+
statics like `propTypes`, `childContextTypes`, `contextTypes`, and
158+
`displayName` and transforms them to `static` properties at the very top.
159+
like `static displayName = 'Counter';`
160+
* TODO do we bind stuff in the `static` object?
161+
* Takes `getDefaultProps` and inlines it as a static `defaultProps`.
162+
If `getDefaultProps` is defined as a function with a single statement that
163+
returns an object, it optimizes and transforms
164+
`getDefaultProps() { return {foo: 'bar'}; }` into
165+
`static defaultProps = {foo: 'bar'};`. If `getDefaultProps` contains more than
166+
one statement it will transform into a self-invoking function like this:
167+
`static defaultProps = (function() {…})();`. Note that this means that the function
168+
will be executed only a single time per app-lifetime. In practice this
169+
hasn't caused any issues – `getDefaultProps` should not contain any
170+
side-effects.
171+
* Transforms class methods to arrow functions as class property initializers
172+
(i.e., to bind them) if methods are referenced without being
173+
called directly. It checks for `this.foo` but also traces variable
174+
assignments like `var self = this; self.foo`. It does not bind functions
175+
from the React API (lifecycle methods) and ignores functions that are being
176+
called directly (unless it is both called directly and passed around to
177+
somewhere else)
178+
* TODO how do we handle `getInitialState`?
179+
* If we reference `this.props` in `getInitialState` then it
180+
has to be in the constructor
181+
* Otherwise it's simple and just make it a property initializer
182+
* TODO [???] When `--no-super-class` is passed it only optionally extends
183+
`React.Component` when `setState` or `forceUpdate` are used within the
184+
class.
185+
186+
The constructor logic is as follows:
187+
188+
* Call `super(props, context)` if the base class needs to be extended.
189+
* Bind all functions that are passed around,
190+
like `this.foo = this.foo.bind(this)`
191+
* Inline `getInitialState` (and remove `getInitialState` from the spec). It
192+
also updates access of `this.props.foo` to `props.foo` and adds `props` as
193+
argument to the constructor. This is necessary in the case when the base
194+
class does not need to be extended where `this.props` will only be set by
195+
React after the constructor has been run.
196+
* Changes `return StateObject` from `getInitialState` to assign `this.state`
197+
directly.
198+
149199
### Recast Options
150200

151201
Options to [recast](https://github.com/benjamn/recast)'s printer can be provided

transforms/__testfixtures__/property-initializer-2.input.js

Lines changed: 28 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,41 @@
22

33
var React = require('React');
44

5+
var ComponentWithNonSimpleInitialState = React.createClass({
6+
statics: {
7+
iDontKnowWhyYouNeedThis: true, // but comment it
8+
foo: 'bar',
9+
},
10+
11+
getInitialState: function() {
12+
return {
13+
counter: this.props.initialNumber + 1,
14+
};
15+
},
16+
17+
render: function() {
18+
return (
19+
<div>{this.state.counter}</div>
20+
);
21+
},
22+
});
23+
524
// Comment
625
module.exports = React.createClass({
726
propTypes: {
827
foo: React.PropTypes.bool,
928
},
1029

11-
getInitialState: function() {
30+
getDefaultProps: function() {
31+
return {
32+
foo: 12,
33+
};
34+
},
35+
36+
getInitialState: function() { // non-simple
37+
var data = 'bar';
1238
return {
13-
foo: 'bar',
39+
bar: data,
1440
};
1541
},
1642

transforms/__testfixtures__/property-initializer-2.output.js

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,20 +2,42 @@
22

33
var React = require('React');
44

5-
// Comment
6-
module.exports = class extends React.Component {
7-
static propTypes = {
8-
foo: React.PropTypes.bool,
9-
};
5+
class ComponentWithNonSimpleInitialState extends React.Component {
6+
static foo = 'bar';
7+
static iDontKnowWhyYouNeedThis = true; // but comment it
108

119
constructor(props, context) {
1210
super(props, context);
1311

1412
this.state = {
15-
foo: 'bar',
13+
counter: props.initialNumber + 1,
1614
};
1715
}
1816

17+
render() {
18+
return (
19+
<div>{this.state.counter}</div>
20+
);
21+
}
22+
}
23+
24+
// Comment
25+
module.exports = class extends React.Component {
26+
static defaultProps = {
27+
foo: 12,
28+
};
29+
30+
static propTypes = {
31+
foo: React.PropTypes.bool,
32+
};
33+
34+
state = function() { // non-simple
35+
var data = 'bar';
36+
return {
37+
bar: data,
38+
};
39+
}();
40+
1941
render() {
2042
return <div />;
2143
}

transforms/__testfixtures__/property-initializer.input.js

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ var MyComponent2 = React.createClass({
2626
getDefaultProps: function() {
2727
return {a: 1};
2828
},
29-
foo: function(): void {
29+
foo: function() { // flow annotations dont work for now
3030
pass(this.foo);
3131
this.forceUpdate();
3232
},
@@ -61,11 +61,11 @@ var MyComponent3 = React.createClass({
6161
};
6262
},
6363

64-
_renderText: function(text: string): ReactElement<any> {
64+
_renderText: function(text: string) { // TODO no return type yet
6565
return <Text text={text} />;
6666
},
6767

68-
_renderImageRange: function(text: string, range): ReactElement<any> {
68+
_renderImageRange: function(text: string, range) { // TODO no return type yet
6969
var image = range.image;
7070
if (image) {
7171
return (
@@ -82,7 +82,7 @@ var MyComponent3 = React.createClass({
8282
dontAutobindMe: function(): number { return 12; },
8383

8484
// Function comment
85-
_renderRange: function(text: string, range, bla: Promise<string>): ReactElement<any> {
85+
_renderRange: function(text: string, range, bla: Promise<string>) {
8686
var self = this;
8787

8888
self.dontAutobindMe();

transforms/__testfixtures__/property-initializer.output.js

Lines changed: 46 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -25,54 +25,47 @@ class MyComponent extends React.Component {
2525

2626
// Class comment
2727
class MyComponent2 extends React.Component {
28-
constructor(props, context) {
29-
super(props, context);
30-
this.foo = this.foo.bind(this);
31-
}
28+
static defaultProps = {a: 1};
3229

33-
foo(): void {
30+
foo = () => { // flow annotations dont work for now
3431
pass(this.foo);
3532
this.forceUpdate();
36-
}
33+
};
3734
}
3835

39-
MyComponent2.defaultProps = {a: 1};
40-
4136
class MyComponent3 extends React.Component {
37+
static defaultProps = function() {
38+
unboundFunc();
39+
return {
40+
linkifyEntities: true,
41+
highlightEntities: false,
42+
};
43+
}();
44+
45+
static funcThatDoesNothing = function(): void {};
46+
47+
static propTypes = {
48+
highlightEntities: React.PropTypes.bool,
49+
linkifyEntities: React.PropTypes.bool,
50+
text: React.PropTypes.shape({
51+
text: React.PropTypes.string,
52+
ranges: React.PropTypes.array,
53+
}).isRequired,
54+
};
55+
56+
static someThing = 10;
57+
4258
constructor(props, context) {
4359
super(props, context);
44-
this._renderRange = this._renderRange.bind(this);
45-
this._renderText = this._renderText.bind(this);
46-
this.autobindMe = this.autobindMe.bind(this);
4760
props.foo();
4861

4962
this.state = {
5063
heyoo: 23,
5164
};
5265
}
5366

54-
_renderText(text: string): ReactElement<any> {
55-
return <Text text={text} />;
56-
}
57-
58-
_renderImageRange(text: string, range): ReactElement<any> {
59-
var image = range.image;
60-
if (image) {
61-
return (
62-
<Image
63-
src={image.uri}
64-
height={image.height / image.scale}
65-
width={image.width / image.scale}
66-
/>
67-
);
68-
}
69-
}
70-
71-
autobindMe() {}
72-
dontAutobindMe(): number { return 12; }
73-
7467
// Function comment
75-
_renderRange(text: string, range, bla: Promise<string>): ReactElement<any> {
68+
_renderRange = (text: string, range, bla: Promise<string>) => {
7669
var self = this;
7770

7871
self.dontAutobindMe();
@@ -95,8 +88,29 @@ class MyComponent3 extends React.Component {
9588
}
9689

9790
return text;
91+
};
92+
93+
_renderText = (text: string) => { // TODO no return type yet
94+
return <Text text={text} />;
95+
};
96+
97+
autobindMe = () => {};
98+
99+
_renderImageRange(text: string, range) { // TODO no return type yet
100+
var image = range.image;
101+
if (image) {
102+
return (
103+
<Image
104+
src={image.uri}
105+
height={image.height / image.scale}
106+
width={image.width / image.scale}
107+
/>
108+
);
109+
}
98110
}
99111

112+
dontAutobindMe(): number { return 12; }
113+
100114
/* This is a comment */
101115
render() {
102116
var content = this.props.text;
@@ -111,27 +125,6 @@ class MyComponent3 extends React.Component {
111125
}
112126
}
113127

114-
MyComponent3.defaultProps = function() {
115-
unboundFunc();
116-
return {
117-
linkifyEntities: true,
118-
highlightEntities: false,
119-
};
120-
}();
121-
122-
MyComponent3.funcThatDoesNothing = function(): void {};
123-
124-
MyComponent3.propTypes = {
125-
highlightEntities: React.PropTypes.bool,
126-
linkifyEntities: React.PropTypes.bool,
127-
text: React.PropTypes.shape({
128-
text: React.PropTypes.string,
129-
ranges: React.PropTypes.array,
130-
}).isRequired,
131-
};
132-
133-
MyComponent3.someThing = 10;
134-
135128
var MyComponent4 = React.createClass({
136129
foo: callMeMaybe(),
137130
render: function() {},

transforms/__tests__/property-initializer-test.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,5 +11,5 @@
1111
'use strict';
1212

1313
const defineTest = require('jscodeshift/dist/testUtils').defineTest;
14-
// defineTest(__dirname, 'property-initializer');
14+
defineTest(__dirname, 'property-initializer');
1515
defineTest(__dirname, 'property-initializer', null, 'property-initializer-2');

0 commit comments

Comments
 (0)