In React 0.12, we're making a core change to how React.createClass(...)
and JSX works.
If you're using JSX in the typical way for all (and only) React components, then this transition will be seamless. Otherwise there are some minor breaking changes described below.
Currently var Button = React.createClass(...)
does two things. It creates a class and a helper function to create ReactElements. It is essentially equivalent to this:
class ButtonClass {
}
function ButtonFactory(...args) {
return React.createElement(ButtonClass, ...args);
}
module.exports = ButtonFactory;
Then you access this in the consuming component by invoking the ButtonFactory.
var Button = require('Button');
class App {
render() {
return Button({ prop: 'foo '}); // ReactElement
}
}
Conceptually this is the wrong model. The source component should not be responsible for the output of App.
There are a few problems with this:
- ES6 classes can't be directly exported, they need to be wrapped.
- There's no convenient way to access the actual class and it's confusing which one you're using.
- Static methods are wrapped in helpers that are not real function. As a convenience.
- Auto-mocking destroys the factory so there is no way to test the result of render without disabling mocking.
- Factories can be wrapped by other factories that returns something different than ReactElements. Making testing and optimizations impossible.
- Languages with specialized features for object management have to defer to React instead of using the built-in features.
In future versions of React the same var Button = React.createClass(...)
will start producing a simple class:
class Button {
}
module.exports = Button;
This means that you can't use that function directly to create a ReactElement. Instead, the consumer will have to create a helper:
var Button = React.createFactory(require('Button'));
class App {
render() {
return Button({ prop: 'foo '}); // ReactElement
}
}
We simply move React.createFactory
from inside React.createClass
to your consuming module. As a convenience, JSX will automatically create a factory for you. If you're using JSX you don't need to change your code:
var Button = require('Button');
class App {
render() {
return <Button prop="foo" />; // ReactElement
}
}
JSX Depends on React
You used to be able to use JSX without requiring React. That's no longer possible.
/** @jsx React.DOM **/
var Foo = require('foo');
function helper() {
return <Foo />; // Throws an error "React is not defined"
}
You'll need to require React in the modules where you use JSX. However, you no longer need to use the @jsx
docblock directive!
var React = require('react');
var Foo = require('foo');
function helper() {
return <Foo />; // yay! It works.
}
JSX Doesn't Work With Arbitrary Functions
It used to be possible to use JSX with non-React components. I.e. simple function calls:
function Foo(props) { } // Not a React component
function helper() {
return <Foo prop="foo" />; // Throws an error
}
This will no longer work since Foo
is not a React component.
Non-JSX Function Calls Must Be Wrapped in createFactory
It will no longer be possible to invoke a React component as a function. You can either use JSX, or React.createFactory
.
var Foo = require('foo'); // A React component
function helper() {
return Foo({ props: 'foo' }); // Throws an error
}
You can use either JSX or wrap it in React.createFactory
:
var React = require('react');
var Foo = React.createFactory(require('foo'));
function helper() {
return Foo({ props: 'foo' }); // yay! It works.
}
Factories Will Not Have Static Methods
Unfortunately factories will not proxy static methods but you can still access them directly on the class:
var React = require('react');
var FooClass = require('foo');
var Foo = React.createFactory(FooClass);
function helper() {
FooClass.foo(); // static method
return Foo({ props: 'foo' }); // ReactElement
}
If you use JSX, this will be managed automatically for you.
Mock Components Won't Be Fired
You can't assert that a component is used by the number of time a mock is called.
var ComponentA = require('A'); // mock
var ComponentB = require('B'); // B renders A
render(<ComponentB />);
expect(ComponentA.mock.calls.length).toBe(1); // this is no longer called
Intermediate Help
We realize that you won't be able to upgrade all your code at once. To help you out, we're providing an intermediate version that works the same as before but it logs warnings when you're using an unsupported pattern. I.e. if you're using abitrary functions in JSX, or calling React classes directly.
Simpler API for ES6 Classes
Future React will be able to export ES6 classes directly from modules. Instead of:
class FooClass {
}
export var Foo = React.createFactory(FooClass); // ugly
You can just export the class directly:
export class Foo {
}
It's easy to access and instantiate the class if you need to. E.g. for testing.
Static Methods are Just Static Method
Static methods are currently exposed as wrappers around the inner class's function.
var Foo = React.createClass({
statics: {
foo: function() {
return this === Foo;
}
}
});
Foo.foo(); // false!
With this change, static methods can be exposed directly:
class Foo {
static foo() {
return this === Foo;
}
}
Foo.foo(); // true!
Auto-mocking Just Works
If you use jest for unit testing, then mocks are automatically created for every module. This means that if you try to invoke a module as a function it will just return undefined
.
var Button = require('Button'); // mock
class App {
render() {
return <Button prop="foo" />;
}
}
If you currently call new App().render()
it will return undefined
in a jest environment. With the new changes, this will return a ReactElement that you can assert on. E.g.
expect(new App().render().type).toBe(Button); // This just works
Optimizations
Since it's now the responsibility of the consumer to create a ReactElement efficiently, that opens up a lot of opportunities for us to do optimizations in the JS transforms that may be dependent on how the consumer uses them. For example inline objects, pooling and reusing constant objects.
var Button = require('Button'); // mock
var sameEveryTime = { type: Button, props: { prop: "foo" } };
class App {
render() {
return sameEveryTime;
}
}
This should enable significant performance improvements to large React application.
Language Interop
Certain languages that compile to JS have various language features that can be used to create ReactElements efficiently. By moving it into the consumer, these don't have to learn how to interop with other components written in other languages. Each component is stand-alone and as long as it works with React it doesn't have to know how other components are built.
Ah, right.
Button
needs to be a factory, not a class. Annotations probably won't work ;/ (unless you're willing deprecateButton()
and usenew Button()
everywhere).