Skip to content

Commit b91396b

Browse files
mrochzpao
authored andcommitted
Contexts
Summary: adds `this.context` which you can think of as implicit props, which are passed automatically down the //ownership// hierarchy. Contexts should be used sparingly, since they essentially allow components to communicate with descendants (in the ownership sense, not parenthood sense), which is not usually a good idea. You probably would only use contexts in places where you'd normally use a global, but contexts allow you to override them for certain view subtrees which you can't do with globals. The context starts out `null`: var RootComponent = React.createClass({ render: function() { // this.context === null } }); You should **never** mutate the context directly, just like props and state. You can change the context of your children (the ones you own, not `this.props.children` or via other props) using the new `withContext` method on `React`: var RootComponent = React.createClass({ render: function() { // this.context === null var children = React.withContext({foo: 'a', bar: 'b'}, () => ( // In ChildComponent#render, this.context === {foo: 'a', bar: 'b'} <ChildComponent /> )); // this.context === null } }); Contexts are merged, so a component can override its owner's context **for its children**: var ChildComponent = React.createClass({ render: function() { // this.context === {foo: 'a', bar: 'b'} (for the caller above) var children = React.withContext({foo: 'c'},() => ( // In GrandchildComponent#render, // this.context === {foo: 'c', bar: 'b'} <GrandchildComponent /> )); // this.context === {foo: 'a', bar: 'b'} } });
1 parent 7df127d commit b91396b

11 files changed

+455
-30
lines changed

src/addons/transitions/ReactTransitionableChild.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ var ReactTransitionableChild = React.createClass({
138138
}
139139
},
140140

141-
componentDidUpdate: function(prevProps, prevState, node) {
141+
componentDidUpdate: function(prevProps, prevState, prevContext, node) {
142142
if (prevProps.children && !this.props.children) {
143143
this.transition('leave', true, this.props.onDoneLeaving);
144144
}

src/core/React.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020

2121
var ReactComponent = require('ReactComponent');
2222
var ReactCompositeComponent = require('ReactCompositeComponent');
23+
var ReactContext = require('ReactContext');
2324
var ReactCurrentOwner = require('ReactCurrentOwner');
2425
var ReactDOM = require('ReactDOM');
2526
var ReactDOMComponent = require('ReactDOMComponent');
@@ -53,6 +54,7 @@ var React = {
5354
unmountAndReleaseReactRootNode: ReactMount.unmountAndReleaseReactRootNode,
5455
isValidClass: ReactCompositeComponent.isValidClass,
5556
isValidComponent: ReactComponent.isValidComponent,
57+
withContext: ReactContext.withContext,
5658
__internals: {
5759
Component: ReactComponent,
5860
CurrentOwner: ReactCurrentOwner,

src/core/ReactCompositeComponent.js

Lines changed: 157 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -19,11 +19,13 @@
1919
"use strict";
2020

2121
var ReactComponent = require('ReactComponent');
22+
var ReactContext = require('ReactContext');
2223
var ReactCurrentOwner = require('ReactCurrentOwner');
2324
var ReactErrorUtils = require('ReactErrorUtils');
2425
var ReactOwner = require('ReactOwner');
2526
var ReactPerf = require('ReactPerf');
2627
var ReactPropTransferer = require('ReactPropTransferer');
28+
var ReactPropTypeLocations = require('ReactPropTypeLocations');
2729
var ReactUpdates = require('ReactUpdates');
2830

2931
var invariant = require('invariant');
@@ -98,6 +100,21 @@ var ReactCompositeComponentInterface = {
98100
*/
99101
propTypes: SpecPolicy.DEFINE_MANY_MERGED,
100102

103+
/**
104+
* Definition of context types for this component.
105+
*
106+
* @type {object}
107+
* @optional
108+
*/
109+
contextTypes: SpecPolicy.DEFINE_MANY_MERGED,
110+
111+
/**
112+
* Definition of context types this component sets for its children.
113+
*
114+
* @type {object}
115+
* @optional
116+
*/
117+
childContextTypes: SpecPolicy.DEFINE_MANY_MERGED,
101118

102119

103120
// ==== Definition methods ====
@@ -130,6 +147,12 @@ var ReactCompositeComponentInterface = {
130147
*/
131148
getInitialState: SpecPolicy.DEFINE_MANY_MERGED,
132149

150+
/**
151+
* @return {object}
152+
* @optional
153+
*/
154+
getChildContext: SpecPolicy.DEFINE_MANY_MERGED,
155+
133156
/**
134157
* Uses props from `this.props` and state from `this.state` to render the
135158
* structure of the component.
@@ -214,14 +237,16 @@ var ReactCompositeComponentInterface = {
214237

215238
/**
216239
* Invoked when the component is about to update due to a transition from
217-
* `this.props` and `this.state` to `nextProps` and `nextState`.
240+
* `this.props`, `this.state` and `this.context` to `nextProps`, `nextState`
241+
* and `nextContext`.
218242
*
219243
* Use this as an opportunity to perform preparation before an update occurs.
220244
*
221245
* NOTE: You **cannot** use `this.setState()` in this method.
222246
*
223247
* @param {object} nextProps
224248
* @param {?object} nextState
249+
* @param {?object} nextContext
225250
* @param {ReactReconcileTransaction} transaction
226251
* @optional
227252
*/
@@ -235,6 +260,7 @@ var ReactCompositeComponentInterface = {
235260
*
236261
* @param {object} prevProps
237262
* @param {?object} prevState
263+
* @param {?object} prevContext
238264
* @param {DOMElement} rootNode DOM element representing the component.
239265
* @optional
240266
*/
@@ -288,6 +314,12 @@ var RESERVED_SPEC_KEYS = {
288314
}
289315
}
290316
},
317+
childContextTypes: function(Constructor, childContextTypes) {
318+
Constructor.childContextTypes = childContextTypes;
319+
},
320+
contextTypes: function(Constructor, contextTypes) {
321+
Constructor.contextTypes = contextTypes;
322+
},
291323
propTypes: function(Constructor, propTypes) {
292324
Constructor.propTypes = propTypes;
293325
}
@@ -512,8 +544,14 @@ var ReactCompositeComponentMixin = {
512544
construct: function(initialProps, children) {
513545
// Children can be either an array or more than one argument
514546
ReactComponent.Mixin.construct.apply(this, arguments);
547+
515548
this.state = null;
516549
this._pendingState = null;
550+
551+
this.context = this._processContext(ReactContext.current);
552+
this._currentContext = ReactContext.current;
553+
this._pendingContext = null;
554+
517555
this._compositeLifeCycleState = null;
518556
},
519557

@@ -659,6 +697,64 @@ var ReactCompositeComponentMixin = {
659697
ReactUpdates.enqueueUpdate(this, callback);
660698
},
661699

700+
/**
701+
* Filters the context object to only contain keys specified in
702+
* `contextTypes`, and asserts that they are valid.
703+
*
704+
* @param {object} context
705+
* @return {?object}
706+
* @private
707+
*/
708+
_processContext: function(context) {
709+
var maskedContext = null;
710+
var contextTypes = this.constructor.contextTypes;
711+
if (contextTypes) {
712+
maskedContext = {};
713+
for (var contextName in contextTypes) {
714+
maskedContext[contextName] = context[contextName];
715+
}
716+
this._checkPropTypes(
717+
contextTypes,
718+
maskedContext,
719+
ReactPropTypeLocations.context
720+
);
721+
}
722+
return maskedContext;
723+
},
724+
725+
/**
726+
* @param {object} currentContext
727+
* @return {object}
728+
* @private
729+
*/
730+
_processChildContext: function(currentContext) {
731+
var childContext = this.getChildContext && this.getChildContext();
732+
var displayName = this.constructor.displayName || 'ReactCompositeComponent';
733+
if (childContext) {
734+
invariant(
735+
typeof this.constructor.childContextTypes === 'object',
736+
'%s.getChildContext(): childContextTypes must be defined in order to ' +
737+
'use getChildContext().',
738+
displayName
739+
);
740+
this._checkPropTypes(
741+
this.constructor.childContextTypes,
742+
childContext,
743+
ReactPropTypeLocations.childContext
744+
);
745+
for (var name in childContext) {
746+
invariant(
747+
name in this.constructor.childContextTypes,
748+
'%s.getChildContext(): key "%s" is not defined in childContextTypes.',
749+
displayName,
750+
name
751+
);
752+
}
753+
return merge(currentContext, childContext);
754+
}
755+
return currentContext;
756+
},
757+
662758
/**
663759
* Processes props by setting default values for unspecified props and
664760
* asserting that the props are valid.
@@ -667,21 +763,32 @@ var ReactCompositeComponentMixin = {
667763
* @private
668764
*/
669765
_processProps: function(props) {
670-
var propName;
671766
var defaultProps = this._defaultProps;
672-
for (propName in defaultProps) {
767+
for (var propName in defaultProps) {
673768
if (typeof props[propName] === 'undefined') {
674769
props[propName] = defaultProps[propName];
675770
}
676771
}
677772
var propTypes = this.constructor.propTypes;
678773
if (propTypes) {
679-
var componentName = this.constructor.displayName;
680-
for (propName in propTypes) {
681-
var checkProp = propTypes[propName];
682-
if (checkProp) {
683-
checkProp(props, propName, componentName);
684-
}
774+
this._checkPropTypes(propTypes, props, ReactPropTypeLocations.prop);
775+
}
776+
},
777+
778+
/**
779+
* Assert that the props are valid
780+
*
781+
* @param {object} propTypes Map of prop name to a ReactPropType
782+
* @param {object} props
783+
* @param {string} location e.g. "prop", "context", "child context"
784+
* @private
785+
*/
786+
_checkPropTypes: function(propTypes, props, location) {
787+
var componentName = this.constructor.displayName;
788+
for (var propName in propTypes) {
789+
var checkProp = propTypes[propName];
790+
if (checkProp) {
791+
checkProp(props, propName, componentName, location);
685792
}
686793
}
687794
},
@@ -707,6 +814,7 @@ var ReactCompositeComponentMixin = {
707814
_performUpdateIfNecessary: function(transaction) {
708815
if (this._pendingProps == null &&
709816
this._pendingState == null &&
817+
this._pendingContext == null &&
710818
!this._pendingForceUpdate) {
711819
return;
712820
}
@@ -728,17 +836,26 @@ var ReactCompositeComponentMixin = {
728836
var nextState = this._pendingState || this.state;
729837
this._pendingState = null;
730838

839+
var nextContext = this._pendingContext || this._currentContext;
840+
this._pendingContext = null;
841+
731842
if (this._pendingForceUpdate ||
732843
!this.shouldComponentUpdate ||
733-
this.shouldComponentUpdate(nextProps, nextState)) {
844+
this.shouldComponentUpdate(nextProps, nextState, nextContext)) {
734845
this._pendingForceUpdate = false;
735-
// Will set `this.props` and `this.state`.
736-
this._performComponentUpdate(nextProps, nextState, transaction);
846+
// Will set `this.props`, `this.state` and `this.context`.
847+
this._performComponentUpdate(
848+
nextProps,
849+
nextState,
850+
nextContext,
851+
transaction
852+
);
737853
} else {
738854
// If it's determined that a component should not update, we still want
739855
// to set props and state.
740856
this.props = nextProps;
741857
this.state = nextState;
858+
this.context = nextContext;
742859
}
743860

744861
this._compositeLifeCycleState = null;
@@ -750,30 +867,49 @@ var ReactCompositeComponentMixin = {
750867
*
751868
* @param {object} nextProps Next object to set as properties.
752869
* @param {?object} nextState Next object to set as state.
870+
* @param {?object} nextContext Next object to set as context.
753871
* @param {ReactReconcileTransaction} transaction
754872
* @private
755873
*/
756-
_performComponentUpdate: function(nextProps, nextState, transaction) {
874+
_performComponentUpdate: function(
875+
nextProps,
876+
nextState,
877+
nextContext,
878+
transaction
879+
) {
757880
var prevProps = this.props;
758881
var prevState = this.state;
882+
var prevContext = this.context;
759883

760884
if (this.componentWillUpdate) {
761-
this.componentWillUpdate(nextProps, nextState);
885+
this.componentWillUpdate(nextProps, nextState, nextContext);
762886
}
763887

764888
this.props = nextProps;
765889
this.state = nextState;
766890

767-
this.updateComponent(transaction, prevProps, prevState);
891+
this._currentContext = nextContext;
892+
this.context = this._processContext(nextContext);
893+
894+
this.updateComponent(transaction, prevProps, prevState, prevContext);
768895

769896
if (this.componentDidUpdate) {
770897
transaction.getReactMountReady().enqueue(
771898
this,
772-
this.componentDidUpdate.bind(this, prevProps, prevState)
899+
this.componentDidUpdate.bind(this, prevProps, prevState, prevContext)
773900
);
774901
}
775902
},
776903

904+
receiveComponent: function(nextComponent, transaction) {
905+
this._pendingContext = nextComponent._currentContext;
906+
ReactComponent.Mixin.receiveComponent.call(
907+
this,
908+
nextComponent,
909+
transaction
910+
);
911+
},
912+
777913
/**
778914
* Updates the component's currently mounted DOM representation.
779915
*
@@ -783,13 +919,14 @@ var ReactCompositeComponentMixin = {
783919
* @param {ReactReconcileTransaction} transaction
784920
* @param {object} prevProps
785921
* @param {?object} prevState
922+
* @param {?object} prevContext
786923
* @internal
787924
* @overridable
788925
*/
789926
updateComponent: ReactPerf.measure(
790927
'ReactCompositeComponent',
791928
'updateComponent',
792-
function(transaction, prevProps, prevState) {
929+
function(transaction, prevProps, prevState, prevContext) {
793930
ReactComponent.Mixin.updateComponent.call(this, transaction, prevProps);
794931
var prevComponent = this._renderedComponent;
795932
var nextComponent = this._renderValidatedComponent();
@@ -851,13 +988,16 @@ var ReactCompositeComponentMixin = {
851988
*/
852989
_renderValidatedComponent: function() {
853990
var renderedComponent;
991+
var previousContext = ReactContext.current;
992+
ReactContext.current = this._processChildContext(this._currentContext);
854993
ReactCurrentOwner.current = this;
855994
try {
856995
renderedComponent = this.render();
857996
} catch (error) {
858997
// IE8 requires `catch` in order to use `finally`.
859998
throw error;
860999
} finally {
1000+
ReactContext.current = previousContext;
8611001
ReactCurrentOwner.current = null;
8621002
}
8631003
invariant(

0 commit comments

Comments
 (0)