mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Minimal implementation of stateless components
Stateless pure-function components give us more opportunity to make performance optimizations. For now, we'll do a minimal implementation which has similar performance characteristics to other components in the interests of shipping 0.14 and allowing people to begin writing code using this pattern; in the future we can refactor to allocate less and avoid other unnecessary work.
This commit is contained in:
@@ -38,6 +38,13 @@ function getDeclarationErrorAddendum(component) {
|
||||
return '';
|
||||
}
|
||||
|
||||
function StatelessComponent(Component) {
|
||||
}
|
||||
StatelessComponent.prototype.render = function() {
|
||||
var Component = ReactInstanceMap.get(this)._currentElement.type;
|
||||
return new Component(this.props, this.context, this.updater);
|
||||
};
|
||||
|
||||
/**
|
||||
* ------------------ The Life-Cycle of a Composite Component ------------------
|
||||
*
|
||||
@@ -126,7 +133,24 @@ var ReactCompositeComponentMixin = {
|
||||
var Component = this._currentElement.type;
|
||||
|
||||
// Initialize the public class
|
||||
var inst = new Component(publicProps, publicContext, ReactUpdateQueue);
|
||||
var inst;
|
||||
var renderedElement;
|
||||
|
||||
if (__DEV__) {
|
||||
ReactCurrentOwner.current = this;
|
||||
try {
|
||||
inst = new Component(publicProps, publicContext, ReactUpdateQueue);
|
||||
} finally {
|
||||
ReactCurrentOwner.current = null;
|
||||
}
|
||||
} else {
|
||||
inst = new Component(publicProps, publicContext, ReactUpdateQueue);
|
||||
}
|
||||
|
||||
if (inst === null || inst === false || ReactElement.isValidElement(inst)) {
|
||||
renderedElement = inst;
|
||||
inst = new StatelessComponent(Component);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
// This will throw later in _renderValidatedComponent, but add an early
|
||||
@@ -231,7 +255,10 @@ var ReactCompositeComponentMixin = {
|
||||
}
|
||||
}
|
||||
|
||||
var renderedElement = this._renderValidatedComponent();
|
||||
// If not a stateless component, we now render
|
||||
if (renderedElement === undefined) {
|
||||
renderedElement = this._renderValidatedComponent();
|
||||
}
|
||||
|
||||
this._renderedComponent = this._instantiateReactComponent(
|
||||
renderedElement
|
||||
@@ -265,6 +292,7 @@ var ReactCompositeComponentMixin = {
|
||||
|
||||
ReactReconciler.unmountComponent(this._renderedComponent);
|
||||
this._renderedComponent = null;
|
||||
this._instance = null;
|
||||
|
||||
// Reset pending fields
|
||||
// Even if this component is scheduled for another update in ReactUpdates,
|
||||
@@ -759,6 +787,7 @@ var ReactCompositeComponentMixin = {
|
||||
*/
|
||||
attachRef: function(ref, component) {
|
||||
var inst = this.getPublicInstance();
|
||||
invariant(inst != null, 'Stateless function components cannot have refs.');
|
||||
var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
|
||||
refs[ref] = component.getPublicInstance();
|
||||
},
|
||||
@@ -800,7 +829,11 @@ var ReactCompositeComponentMixin = {
|
||||
* @internal
|
||||
*/
|
||||
getPublicInstance: function() {
|
||||
return this._instance;
|
||||
var inst = this._instance;
|
||||
if (inst instanceof StatelessComponent) {
|
||||
return null;
|
||||
}
|
||||
return inst;
|
||||
},
|
||||
|
||||
// Stub
|
||||
|
||||
@@ -25,16 +25,6 @@ function enqueueUpdate(internalInstance) {
|
||||
}
|
||||
|
||||
function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
ReactCurrentOwner.current == null,
|
||||
'%s(...): Cannot update during an existing state transition ' +
|
||||
'(such as within `render`). Render methods should be a pure function ' +
|
||||
'of props and state.',
|
||||
callerName
|
||||
);
|
||||
}
|
||||
|
||||
var internalInstance = ReactInstanceMap.get(publicInstance);
|
||||
if (!internalInstance) {
|
||||
if (__DEV__) {
|
||||
@@ -54,6 +44,16 @@ function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
ReactCurrentOwner.current == null,
|
||||
'%s(...): Cannot update during an existing state transition ' +
|
||||
'(such as within `render`). Render methods should be a pure function ' +
|
||||
'of props and state.',
|
||||
callerName
|
||||
);
|
||||
}
|
||||
|
||||
return internalInstance;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,184 @@
|
||||
/**
|
||||
* Copyright 2013-2015, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*
|
||||
* @emails react-core
|
||||
*/
|
||||
|
||||
'use strict';
|
||||
|
||||
var React;
|
||||
var ReactTestUtils;
|
||||
|
||||
function StatelessComponent(props) {
|
||||
return <div>{props.name}</div>;
|
||||
}
|
||||
|
||||
describe('ReactStatelessComponent', function() {
|
||||
|
||||
beforeEach(function() {
|
||||
React = require('React');
|
||||
ReactTestUtils = require('ReactTestUtils');
|
||||
});
|
||||
|
||||
it('should render stateless component', function() {
|
||||
var el = document.createElement('div');
|
||||
React.render(<StatelessComponent name="A" />, el);
|
||||
|
||||
expect(el.textContent).toBe('A');
|
||||
});
|
||||
|
||||
it('should update stateless component', function() {
|
||||
var Parent = React.createClass({
|
||||
render() {
|
||||
return <StatelessComponent {...this.props} />;
|
||||
},
|
||||
});
|
||||
|
||||
var el = document.createElement('div');
|
||||
React.render(<Parent name="A" />, el);
|
||||
expect(el.textContent).toBe('A');
|
||||
|
||||
React.render(<Parent name="B" />, el);
|
||||
expect(el.textContent).toBe('B');
|
||||
});
|
||||
|
||||
it('should unmount stateless component', function() {
|
||||
var container = document.createElement('div');
|
||||
|
||||
React.render(<StatelessComponent name="A" />, container);
|
||||
expect(container.textContent).toBe('A');
|
||||
|
||||
React.unmountComponentAtNode(container);
|
||||
expect(container.textContent).toBe('');
|
||||
});
|
||||
|
||||
it('should pass context thru stateless component', function() {
|
||||
var Child = React.createClass({
|
||||
contextTypes: {
|
||||
test: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <div>{this.context.test}</div>;
|
||||
},
|
||||
});
|
||||
|
||||
function Parent() {
|
||||
return <Child />;
|
||||
}
|
||||
|
||||
var GrandParent = React.createClass({
|
||||
childContextTypes: {
|
||||
test: React.PropTypes.string.isRequired,
|
||||
},
|
||||
|
||||
getChildContext() {
|
||||
return {test: this.props.test};
|
||||
},
|
||||
|
||||
render: function() {
|
||||
return <Parent />;
|
||||
},
|
||||
});
|
||||
|
||||
var el = document.createElement('div');
|
||||
React.render(<GrandParent test="test" />, el);
|
||||
|
||||
expect(el.textContent).toBe('test');
|
||||
|
||||
React.render(<GrandParent test="mest" />, el);
|
||||
|
||||
expect(el.textContent).toBe('mest');
|
||||
});
|
||||
|
||||
it('should support module pattern components', function() {
|
||||
function Child({test}) {
|
||||
return {
|
||||
render() {
|
||||
return <div>{test}</div>;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
var el = document.createElement('div');
|
||||
React.render(<Child test="test" />, el);
|
||||
|
||||
expect(el.textContent).toBe('test');
|
||||
});
|
||||
|
||||
it('should throw on string refs in pure functions', function() {
|
||||
function Child() {
|
||||
return <div ref="me" />;
|
||||
}
|
||||
|
||||
expect(function() {
|
||||
ReactTestUtils.renderIntoDocument(<Child test="test" />);
|
||||
}).toThrow(
|
||||
'Invariant Violation: Stateless function components cannot have refs.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should provide a null ref', function() {
|
||||
function Child() {
|
||||
return <div />;
|
||||
}
|
||||
|
||||
var comp = ReactTestUtils.renderIntoDocument(<Child />);
|
||||
expect(comp).toBe(null);
|
||||
});
|
||||
|
||||
it('should use correct name in key warning', function() {
|
||||
function Child() {
|
||||
return <div>{[<span />]}</div>;
|
||||
}
|
||||
|
||||
spyOn(console, 'error');
|
||||
ReactTestUtils.renderIntoDocument(<Child />);
|
||||
expect(console.error.argsForCall.length).toBe(1);
|
||||
expect(console.error.argsForCall[0][0]).toContain('a unique "key" prop');
|
||||
expect(console.error.argsForCall[0][0]).toContain('Child');
|
||||
});
|
||||
|
||||
it('should support default props and prop types', function() {
|
||||
function Child(props) {
|
||||
return <div>{props.test}</div>;
|
||||
}
|
||||
Child.defaultProps = {test: 2};
|
||||
Child.propTypes = {test: React.PropTypes.string};
|
||||
|
||||
spyOn(console, 'error');
|
||||
ReactTestUtils.renderIntoDocument(<Child />);
|
||||
expect(console.error.argsForCall.length).toBe(1);
|
||||
expect(console.error.argsForCall[0][0]).toBe(
|
||||
'Warning: Failed propType: Invalid prop `test` of type `number` ' +
|
||||
'supplied to `Child`, expected `string`.'
|
||||
);
|
||||
});
|
||||
|
||||
it('should receive context', function() {
|
||||
var Parent = React.createClass({
|
||||
childContextTypes: {
|
||||
lang: React.PropTypes.string,
|
||||
},
|
||||
getChildContext: function() {
|
||||
return {lang: 'en'};
|
||||
},
|
||||
render: function() {
|
||||
return <Child />;
|
||||
},
|
||||
});
|
||||
function Child(props, context) {
|
||||
return <div>{context.lang}</div>;
|
||||
}
|
||||
Child.contextTypes = {lang: React.PropTypes.string};
|
||||
|
||||
var el = document.createElement('div');
|
||||
React.render(<Parent />, el);
|
||||
expect(el.textContent).toBe('en');
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user