@samitouri / QOS-React-2 / commits / 5a7bd964b4

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.

Ben Alpert committed Aug 7, 2015 at 19:06 UTC 5a7bd964b4265a0e0e87e453fa0d1a4ccb604fe1
3 files changed +230 -13
src/renderers/shared/reconciler/ReactCompositeComponent.js
+36 -3
@@ -38,6 +38,13 @@ function getDeclarationErrorAddendum(component) {
38 return '';
39 }
40
41 +function StatelessComponent(Component) {
42 +}
43 +StatelessComponent.prototype.render = function() {
44 + var Component = ReactInstanceMap.get(this)._currentElement.type;
45 + return new Component(this.props, this.context, this.updater);
46 +};
47 +
48 /**
49 * ------------------ The Life-Cycle of a Composite Component ------------------
50 *
@@ -126,7 +133,24 @@ var ReactCompositeComponentMixin = {
133 var Component = this._currentElement.type;
134
135 // Initialize the public class
129 - var inst = new Component(publicProps, publicContext, ReactUpdateQueue);
136 + var inst;
137 + var renderedElement;
138 +
139 + if (__DEV__) {
140 + ReactCurrentOwner.current = this;
141 + try {
142 + inst = new Component(publicProps, publicContext, ReactUpdateQueue);
143 + } finally {
144 + ReactCurrentOwner.current = null;
145 + }
146 + } else {
147 + inst = new Component(publicProps, publicContext, ReactUpdateQueue);
148 + }
149 +
150 + if (inst === null || inst === false || ReactElement.isValidElement(inst)) {
151 + renderedElement = inst;
152 + inst = new StatelessComponent(Component);
153 + }
154
155 if (__DEV__) {
156 // This will throw later in _renderValidatedComponent, but add an early
@@ -231,7 +255,10 @@ var ReactCompositeComponentMixin = {
255 }
256 }
257
234 - var renderedElement = this._renderValidatedComponent();
258 + // If not a stateless component, we now render
259 + if (renderedElement === undefined) {
260 + renderedElement = this._renderValidatedComponent();
261 + }
262
263 this._renderedComponent = this._instantiateReactComponent(
264 renderedElement
@@ -265,6 +292,7 @@ var ReactCompositeComponentMixin = {
292
293 ReactReconciler.unmountComponent(this._renderedComponent);
294 this._renderedComponent = null;
295 + this._instance = null;
296
297 // Reset pending fields
298 // Even if this component is scheduled for another update in ReactUpdates,
@@ -759,6 +787,7 @@ var ReactCompositeComponentMixin = {
787 */
788 attachRef: function(ref, component) {
789 var inst = this.getPublicInstance();
790 + invariant(inst != null, 'Stateless function components cannot have refs.');
791 var refs = inst.refs === emptyObject ? (inst.refs = {}) : inst.refs;
792 refs[ref] = component.getPublicInstance();
793 },
@@ -800,7 +829,11 @@ var ReactCompositeComponentMixin = {
829 * @internal
830 */
831 getPublicInstance: function() {
803 - return this._instance;
832 + var inst = this._instance;
833 + if (inst instanceof StatelessComponent) {
834 + return null;
835 + }
836 + return inst;
837 },
838
839 // Stub
src/renderers/shared/reconciler/ReactUpdateQueue.js
+10 -10
@@ -25,16 +25,6 @@ function enqueueUpdate(internalInstance) {
25 }
26
27 function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
28 - if (__DEV__) {
29 - warning(
30 - ReactCurrentOwner.current == null,
31 - '%s(...): Cannot update during an existing state transition ' +
32 - '(such as within `render`). Render methods should be a pure function ' +
33 - 'of props and state.',
34 - callerName
35 - );
36 - }
37 -
28 var internalInstance = ReactInstanceMap.get(publicInstance);
29 if (!internalInstance) {
30 if (__DEV__) {
@@ -54,6 +44,16 @@ function getInternalInstanceReadyForUpdate(publicInstance, callerName) {
44 return null;
45 }
46
47 + if (__DEV__) {
48 + warning(
49 + ReactCurrentOwner.current == null,
50 + '%s(...): Cannot update during an existing state transition ' +
51 + '(such as within `render`). Render methods should be a pure function ' +
52 + 'of props and state.',
53 + callerName
54 + );
55 + }
56 +
57 return internalInstance;
58 }
59
src/renderers/shared/reconciler/__tests__/ReactStatelessComponent-test.js new
+184
@@ -0,0 +1,184 @@
1 +/**
2 + * Copyright 2013-2015, Facebook, Inc.
3 + * All rights reserved.
4 + *
5 + * This source code is licensed under the BSD-style license found in the
6 + * LICENSE file in the root directory of this source tree. An additional grant
7 + * of patent rights can be found in the PATENTS file in the same directory.
8 + *
9 + * @emails react-core
10 + */
11 +
12 +'use strict';
13 +
14 +var React;
15 +var ReactTestUtils;
16 +
17 +function StatelessComponent(props) {
18 + return <div>{props.name}</div>;
19 +}
20 +
21 +describe('ReactStatelessComponent', function() {
22 +
23 + beforeEach(function() {
24 + React = require('React');
25 + ReactTestUtils = require('ReactTestUtils');
26 + });
27 +
28 + it('should render stateless component', function() {
29 + var el = document.createElement('div');
30 + React.render(<StatelessComponent name="A" />, el);
31 +
32 + expect(el.textContent).toBe('A');
33 + });
34 +
35 + it('should update stateless component', function() {
36 + var Parent = React.createClass({
37 + render() {
38 + return <StatelessComponent {...this.props} />;
39 + },
40 + });
41 +
42 + var el = document.createElement('div');
43 + React.render(<Parent name="A" />, el);
44 + expect(el.textContent).toBe('A');
45 +
46 + React.render(<Parent name="B" />, el);
47 + expect(el.textContent).toBe('B');
48 + });
49 +
50 + it('should unmount stateless component', function() {
51 + var container = document.createElement('div');
52 +
53 + React.render(<StatelessComponent name="A" />, container);
54 + expect(container.textContent).toBe('A');
55 +
56 + React.unmountComponentAtNode(container);
57 + expect(container.textContent).toBe('');
58 + });
59 +
60 + it('should pass context thru stateless component', function() {
61 + var Child = React.createClass({
62 + contextTypes: {
63 + test: React.PropTypes.string.isRequired,
64 + },
65 +
66 + render: function() {
67 + return <div>{this.context.test}</div>;
68 + },
69 + });
70 +
71 + function Parent() {
72 + return <Child />;
73 + }
74 +
75 + var GrandParent = React.createClass({
76 + childContextTypes: {
77 + test: React.PropTypes.string.isRequired,
78 + },
79 +
80 + getChildContext() {
81 + return {test: this.props.test};
82 + },
83 +
84 + render: function() {
85 + return <Parent />;
86 + },
87 + });
88 +
89 + var el = document.createElement('div');
90 + React.render(<GrandParent test="test" />, el);
91 +
92 + expect(el.textContent).toBe('test');
93 +
94 + React.render(<GrandParent test="mest" />, el);
95 +
96 + expect(el.textContent).toBe('mest');
97 + });
98 +
99 + it('should support module pattern components', function() {
100 + function Child({test}) {
101 + return {
102 + render() {
103 + return <div>{test}</div>;
104 + },
105 + };
106 + }
107 +
108 + var el = document.createElement('div');
109 + React.render(<Child test="test" />, el);
110 +
111 + expect(el.textContent).toBe('test');
112 + });
113 +
114 + it('should throw on string refs in pure functions', function() {
115 + function Child() {
116 + return <div ref="me" />;
117 + }
118 +
119 + expect(function() {
120 + ReactTestUtils.renderIntoDocument(<Child test="test" />);
121 + }).toThrow(
122 + 'Invariant Violation: Stateless function components cannot have refs.'
123 + );
124 + });
125 +
126 + it('should provide a null ref', function() {
127 + function Child() {
128 + return <div />;
129 + }
130 +
131 + var comp = ReactTestUtils.renderIntoDocument(<Child />);
132 + expect(comp).toBe(null);
133 + });
134 +
135 + it('should use correct name in key warning', function() {
136 + function Child() {
137 + return <div>{[<span />]}</div>;
138 + }
139 +
140 + spyOn(console, 'error');
141 + ReactTestUtils.renderIntoDocument(<Child />);
142 + expect(console.error.argsForCall.length).toBe(1);
143 + expect(console.error.argsForCall[0][0]).toContain('a unique "key" prop');
144 + expect(console.error.argsForCall[0][0]).toContain('Child');
145 + });
146 +
147 + it('should support default props and prop types', function() {
148 + function Child(props) {
149 + return <div>{props.test}</div>;
150 + }
151 + Child.defaultProps = {test: 2};
152 + Child.propTypes = {test: React.PropTypes.string};
153 +
154 + spyOn(console, 'error');
155 + ReactTestUtils.renderIntoDocument(<Child />);
156 + expect(console.error.argsForCall.length).toBe(1);
157 + expect(console.error.argsForCall[0][0]).toBe(
158 + 'Warning: Failed propType: Invalid prop `test` of type `number` ' +
159 + 'supplied to `Child`, expected `string`.'
160 + );
161 + });
162 +
163 + it('should receive context', function() {
164 + var Parent = React.createClass({
165 + childContextTypes: {
166 + lang: React.PropTypes.string,
167 + },
168 + getChildContext: function() {
169 + return {lang: 'en'};
170 + },
171 + render: function() {
172 + return <Child />;
173 + },
174 + });
175 + function Child(props, context) {
176 + return <div>{context.lang}</div>;
177 + }
178 + Child.contextTypes = {lang: React.PropTypes.string};
179 +
180 + var el = document.createElement('div');
181 + React.render(<Parent />, el);
182 + expect(el.textContent).toBe('en');
183 + });
184 +});