-
Notifications
You must be signed in to change notification settings - Fork 290
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Class transform with property initializer #54
Conversation
Updated to change the existing |
@keyanzhang Is that the best idea? The benefit I see to keeping the existing |
More food for thought -- if you want to combine them, could the stage-1 items such as property initializers be optional? |
@nathanmarks Yeah, I think we could keep the both or them. Updating the existing one makes it much easier to review the diff, but I can switch to create new files later if necessary. |
For us, I think it makes the most sense to keep the version we're planning to use at Facebook in this repo. If people want the older version without property initializers it's easy to check out an old version – but I don't anticipate that many people will want this codemod script and convert all of the components until property initializers are more stable. |
@gaearon: I added a temporary shrinkwrap file to pin |
👍 I’ll review later today, thank you. |
No, not anymore. Component base class is required in 15.0+.
I think we might want to also support removing |
const hasInconvertibleMixins = classPath => { | ||
if ( | ||
ReactUtils.hasMixins(classPath) && | ||
!ReactUtils.hasSpecificMixins(classPath, ['ReactComponentWithPureRenderMixin']) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
For open source, it’s important that we support PureRenderMixin
here. This is how we refer to it in the docs.
from the React API (lifecycle methods) and ignores functions that are being | ||
called directly (unless it is both called directly and passed around to | ||
somewhere else). | ||
* TODO When `--no-super-class` is passed it only optionally extends |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Don’t forget to remove this TODO 😉
This reverts commit f5f6da0.
e4667f6
to
8b15535
Compare
Shouldn't |
It doesn’t have to be passed unless you’re reading |
Ok, thanks for the explanation ! |
38e6184
to
953386a
Compare
de192a8
to
b59cde3
Compare
Now we search and grab `React.createClass` _call expressions_ directly. The only time that we can't simply replace a `createClass` call path with a new class is when the parent of that is a variable declaration: `var Foo = React.createClass({...});` needs to be replaced entirely with `class Foo extends React.Component {...}` Now we delay checking it and only when we need to replace a path we take a look at `path.parentPath.value.type` to see if it's a variable declarator. With this commit we should be able to mod any kind of anonymous `createClass` calls no matter how they are defined.
b59cde3
to
847de6d
Compare
Congratulations! |
How it works
createClass
call to an ES6 class component when:options['pure-component']
is true, themixins
property is an array and it only contains pure render mixin (the specific module name can be specified usingoptions['mixin-module-name']
, which defaults toreact-addons-pure-render-mixin
)isMounted
,getDOMNode
,replaceProps
,replaceState
orsetProps
it will skip the componentthis.getInitialState()
and/orthis.getDefaultProps()
since an ES6 class component will no longer have these methodsarguments
in methods since arrow functions don't havearguments
. Also please notice thatarguments
should be very carefully used and it's generally better to switch to spread (...args
) insteadgetInitialState()
. Specifically if you have variable declarations likevar props = ...
and the right hand side is notthis.props
then we can't inline the state initialization in theconstructor
due to variable shadowing issuesfoo: getStuff()
) in the class specvar A = React.createClass(spec)
withclass A extends React.Component {spec}
. If a component uses pure render mixin and passes the mixins test (as described above), it will extendReact.PureComponent
insteadrequire
/import
statement that imports pure render mixin when it's no longer being referencedstatics
plus the few special cased statics likechildContextTypes
,contextTypes
,displayName
,getDefaultProps()
, andpropTypes
and transform them tostatic
properties (static propTypes = {...};
)getDefaultProps()
is simple (i.e. it only contains a return statement that returns an object) it will be converted to a simple assignment (static defaultProps = {...};
). Otherwise an IIFE (immediately-invoked function expression) will be created (static defaultProps = function() { ... }();
). Note that this means that the function will be executed only a single time per app-lifetime. In practice this hasn't caused any issues —getDefaultProps
should not contain any side-effectsgetInitialState()
getInitialState()
or thegetInitialState()
function is simple (i.e., it only contains a return statement that returns an object) then we don't need a constructor;state
will be lifted to a property initializer (state = {...};
)this
other thanthis.props
and/orthis.context
, we can't be sure about what you'll need fromthis
. We need to ensure that our property initializers' evaluation order is safe, so we deferstate
's initialization by moving it all the way down until all other property initializers have been initializedgetInitialState()
is not simple, we create aconstructor
and convertgetInitialState()
to an assignment tothis.state
constructor
always haveprops
as the first parametercontext
as the second parameter when (one of) the following things happen ingetInitialState()
:this.context
, orthis.x()
, orthis
is referenced alonethis.props
toprops
and accesses tothis.context
tocontext
since the values will be passed asconstructor
argumentsvar props = this.props;
andvar context = this.context
return {...};
) tothis.state = {...}
return;
after the assignment when the return statement is part of a control flow statement (not a direct child ofgetInitialState()
's body) and not in an inner function declarationonClick = () => {};
). All your Flow annotations will be preservedcreateClass()
and we can make sure that we won't accidentally break stuffpropTypes
and put it on the class (this only happens when there's/* @flow */
in your code andoptions['flow']
istrue
)propTypes
increateClass
calls but not ES6 class components. Here the transformation logic is identical to how Flow treatspropTypes
foo: React.PropTypes.number
is valid when you pass{}
,{foo: null}
, or{foo: undefined}
as props at runtime. However, when Flow infers type from acreateClass
call, only{}
and{foo: undefined}
are valid;{foo: null}
is not. Thus the equivalent type annotation in Flow is actually{foo?: number}
. The question mark on the left hand side indicates{}
and{foo: undefined}
are fine, but whenfoo
is present it must be anumber
propTypes
fields that can't be recognized by Flow,$FlowFixMe
will be usedA few steps left:
--no-super-class
optionreact-addons-pure-render-mixin
var ReactComponentWithPureRenderMixin = require('ReactComponentWithPureRenderMixin')
when transformation happensUpdate: discussed with @spicyj in person and here are the changes/required steps to finish before shipping this:
propTypes
to Flow annotations (props: ...
) (a7f71a3)count: (12: number),
tocount: number = 12;
(231f629)context
and/orprops
inconstructor(props, context)
whenever possibleno-use-before-define
linter ruleNew issues:
getInitialState
(b8bb77a)super
withprops
whengetInitialState
uses onlythis.props
orthis.props.foo
but has no other usage ofthis
(3936f55)Good to fix:
super
withprops
andcontext
whengetInitialState
usesthis
in any other way (3936f55)state
property initializer evaluation whengetInitialState
has any references tothis
(4f64cc2)this.getInitialState()
anywhere (e00677f)arguments
is used inside React methods (59e3671)const props = this.props
ingetInitialState
(d67b6a8)displayName
should not show up twiceBacklog:
const X = wrap(React.createClass(..))
crashes codemod) (19b611a)