@samitouri / QOS-React-2 / commits / 1224a203bb

Inject the update queue into classes

This decouples the stateful imperative API from the class creation. Instead, they get injected into the class from the renderer. Stateful modules should always be injected. As a convenience, just like props/context/refs, we set it up after construction using mutation. That way it is optional to pass it along the super call constructor chain.

Sebastian Markbage committed Jun 17, 2015 at 18:57 UTC 1224a203bb9fa6dbbc41dd6bd907f599b5d5f93b
4 files changed +153 -16
src/isomorphic/classic/class/ReactClass.js
+13 -9
@@ -16,9 +16,10 @@ var ReactElement = require('ReactElement');
16 var ReactErrorUtils = require('ReactErrorUtils');
17 var ReactPropTypeLocations = require('ReactPropTypeLocations');
18 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
19 -var ReactUpdateQueue = require('ReactUpdateQueue');
19 +var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
20
21 var assign = require('Object.assign');
22 +var emptyObject = require('emptyObject');
23 var invariant = require('invariant');
24 var keyMirror = require('keyMirror');
25 var keyOf = require('keyOf');
@@ -712,9 +713,9 @@ var ReactClassMixin = {
713 * type signature and the only use case for this, is to avoid that.
714 */
715 replaceState: function(newState, callback) {
715 - ReactUpdateQueue.enqueueReplaceState(this, newState);
716 + this.updater.enqueueReplaceState(this, newState);
717 if (callback) {
717 - ReactUpdateQueue.enqueueCallback(this, callback);
718 + this.updater.enqueueCallback(this, callback);
719 }
720 },
721
@@ -725,7 +726,7 @@ var ReactClassMixin = {
726 * @final
727 */
728 isMounted: function() {
728 - return ReactUpdateQueue.isMounted(this);
729 + return this.updater.isMounted(this);
730 },
731
732 /**
@@ -738,9 +739,9 @@ var ReactClassMixin = {
739 * @deprecated
740 */
741 setProps: function(partialProps, callback) {
741 - ReactUpdateQueue.enqueueSetProps(this, partialProps);
742 + this.updater.enqueueSetProps(this, partialProps);
743 if (callback) {
743 - ReactUpdateQueue.enqueueCallback(this, callback);
744 + this.updater.enqueueCallback(this, callback);
745 }
746 },
747
@@ -754,9 +755,9 @@ var ReactClassMixin = {
755 * @deprecated
756 */
757 replaceProps: function(newProps, callback) {
757 - ReactUpdateQueue.enqueueReplaceProps(this, newProps);
758 + this.updater.enqueueReplaceProps(this, newProps);
759 if (callback) {
759 - ReactUpdateQueue.enqueueCallback(this, callback);
760 + this.updater.enqueueCallback(this, callback);
761 }
762 },
763 };
@@ -783,7 +784,7 @@ var ReactClass = {
784 * @public
785 */
786 createClass: function(spec) {
786 - var Constructor = function(props, context) {
787 + var Constructor = function(props, context, updater) {
788 // This constructor is overridden by mocks. The argument is used
789 // by mocks to assert on what gets mounted.
790
@@ -802,6 +803,9 @@ var ReactClass = {
803
804 this.props = props;
805 this.context = context;
806 + this.refs = emptyObject;
807 + this.updater = updater || ReactNoopUpdateQueue;
808 +
809 this.state = null;
810
811 // ReactClasses doesn't have constructors. Instead, they use the
src/isomorphic/modern/class/ReactComponent.js
+11 -6
@@ -11,17 +11,22 @@
11
12 'use strict';
13
14 -var ReactUpdateQueue = require('ReactUpdateQueue');
14 +var ReactNoopUpdateQueue = require('ReactNoopUpdateQueue');
15
16 +var emptyObject = require('emptyObject');
17 var invariant = require('invariant');
18 var warning = require('warning');
19
20 /**
21 * Base class helpers for the updating state of a component.
22 */
22 -function ReactComponent(props, context) {
23 +function ReactComponent(props, context, updater) {
24 this.props = props;
25 this.context = context;
26 + this.refs = emptyObject;
27 + // We initialize the default updater but the real one gets injected by the
28 + // renderer.
29 + this.updater = updater || ReactNoopUpdateQueue;
30 }
31
32 /**
@@ -64,9 +69,9 @@ ReactComponent.prototype.setState = function(partialState, callback) {
69 'instead, use forceUpdate().'
70 );
71 }
67 - ReactUpdateQueue.enqueueSetState(this, partialState);
72 + this.updater.enqueueSetState(this, partialState);
73 if (callback) {
69 - ReactUpdateQueue.enqueueCallback(this, callback);
74 + this.updater.enqueueCallback(this, callback);
75 }
76 };
77
@@ -85,9 +90,9 @@ ReactComponent.prototype.setState = function(partialState, callback) {
90 * @protected
91 */
92 ReactComponent.prototype.forceUpdate = function(callback) {
88 - ReactUpdateQueue.enqueueForceUpdate(this);
93 + this.updater.enqueueForceUpdate(this);
94 if (callback) {
90 - ReactUpdateQueue.enqueueCallback(this, callback);
95 + this.updater.enqueueCallback(this, callback);
96 }
97 };
98
src/isomorphic/modern/class/ReactNoopUpdateQueue.js new
+126
@@ -0,0 +1,126 @@
1 +/**
2 + * Copyright 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 + * @providesModule ReactNoopUpdateQueue
10 + */
11 +
12 +'use strict';
13 +
14 +var warning = require('warning');
15 +
16 +function warnTDZ(publicInstance, callerName) {
17 + if (__DEV__) {
18 + warning(
19 + false,
20 + '%s(...): Can only update a mounted or mounting component. ' +
21 + 'This usually means you called %s() on an unmounted component. ' +
22 + 'This is a no-op. Please check the code for the %s component.',
23 + callerName,
24 + callerName,
25 + publicInstance.constructor && publicInstance.constructor.displayName || ''
26 + );
27 + }
28 +}
29 +
30 +/**
31 + * This is the abstract API for an update queue.
32 + */
33 +var ReactNoopUpdateQueue = {
34 +
35 + /**
36 + * Checks whether or not this composite component is mounted.
37 + * @param {ReactClass} publicInstance The instance we want to test.
38 + * @return {boolean} True if mounted, false otherwise.
39 + * @protected
40 + * @final
41 + */
42 + isMounted: function(publicInstance) {
43 + return false;
44 + },
45 +
46 + /**
47 + * Enqueue a callback that will be executed after all the pending updates
48 + * have processed.
49 + *
50 + * @param {ReactClass} publicInstance The instance to use as `this` context.
51 + * @param {?function} callback Called after state is updated.
52 + * @internal
53 + */
54 + enqueueCallback: function(publicInstance, callback) { },
55 +
56 + /**
57 + * Forces an update. This should only be invoked when it is known with
58 + * certainty that we are **not** in a DOM transaction.
59 + *
60 + * You may want to call this when you know that some deeper aspect of the
61 + * component's state has changed but `setState` was not called.
62 + *
63 + * This will not invoke `shouldComponentUpdate`, but it will invoke
64 + * `componentWillUpdate` and `componentDidUpdate`.
65 + *
66 + * @param {ReactClass} publicInstance The instance that should rerender.
67 + * @internal
68 + */
69 + enqueueForceUpdate: function(publicInstance) {
70 + warnTDZ(publicInstance, 'forceUpdate');
71 + },
72 +
73 + /**
74 + * Replaces all of the state. Always use this or `setState` to mutate state.
75 + * You should treat `this.state` as immutable.
76 + *
77 + * There is no guarantee that `this.state` will be immediately updated, so
78 + * accessing `this.state` after calling this method may return the old value.
79 + *
80 + * @param {ReactClass} publicInstance The instance that should rerender.
81 + * @param {object} completeState Next state.
82 + * @internal
83 + */
84 + enqueueReplaceState: function(publicInstance, completeState) {
85 + warnTDZ(publicInstance, 'replaceState');
86 + },
87 +
88 + /**
89 + * Sets a subset of the state. This only exists because _pendingState is
90 + * internal. This provides a merging strategy that is not available to deep
91 + * properties which is confusing. TODO: Expose pendingState or don't use it
92 + * during the merge.
93 + *
94 + * @param {ReactClass} publicInstance The instance that should rerender.
95 + * @param {object} partialState Next partial state to be merged with state.
96 + * @internal
97 + */
98 + enqueueSetState: function(publicInstance, partialState) {
99 + warnTDZ(publicInstance, 'setState');
100 + },
101 +
102 + /**
103 + * Sets a subset of the props.
104 + *
105 + * @param {ReactClass} publicInstance The instance that should rerender.
106 + * @param {object} partialProps Subset of the next props.
107 + * @internal
108 + */
109 + enqueueSetProps: function(publicInstance, partialProps) {
110 + warnTDZ(publicInstance, 'setProps');
111 + },
112 +
113 + /**
114 + * Replaces all of the props.
115 + *
116 + * @param {ReactClass} publicInstance The instance that should rerender.
117 + * @param {object} props New props.
118 + * @internal
119 + */
120 + enqueueReplaceProps: function(publicInstance, props) {
121 + warnTDZ(publicInstance, 'replaceProps');
122 + },
123 +
124 +};
125 +
126 +module.exports = ReactNoopUpdateQueue;
src/renderers/shared/reconciler/ReactCompositeComponent.js
+3 -1
@@ -22,6 +22,7 @@ var ReactPerf = require('ReactPerf');
22 var ReactPropTypeLocations = require('ReactPropTypeLocations');
23 var ReactPropTypeLocationNames = require('ReactPropTypeLocationNames');
24 var ReactReconciler = require('ReactReconciler');
25 +var ReactUpdateQueue = require('ReactUpdateQueue');
26
27 var assign = require('Object.assign');
28 var emptyObject = require('emptyObject');
@@ -130,7 +131,7 @@ var ReactCompositeComponentMixin = {
131 );
132
133 // Initialize the public class
133 - var inst = new Component(publicProps, publicContext);
134 + var inst = new Component(publicProps, publicContext, ReactUpdateQueue);
135
136 if (__DEV__) {
137 // This will throw later in _renderValidatedComponent, but add an early
@@ -150,6 +151,7 @@ var ReactCompositeComponentMixin = {
151 inst.props = publicProps;
152 inst.context = publicContext;
153 inst.refs = emptyObject;
154 + inst.updater = ReactUpdateQueue;
155
156 this._instance = inst;
157