mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Use strings as the type for DOM elements
This makes ReactDOM a simple helper for creating ReactElements with the string tag as the type. The actual class is internal and created by instantiateReactComponent. Configurable using injection. There's not a separate class for each tag. There's just a generic ReactDOMComponent which could take any tag name. Invididual tags can be wrapped. When wrapping happens you can return the same tag again. If the wrapper returns the same string, then we fall back to the generic component. This avoids recursion in a single level wrapper.
This commit is contained in:
+134
-159
@@ -22,41 +22,23 @@
|
||||
var ReactDescriptor = require('ReactDescriptor');
|
||||
var ReactDescriptorValidator = require('ReactDescriptorValidator');
|
||||
var ReactLegacyDescriptor = require('ReactLegacyDescriptor');
|
||||
var ReactDOMComponent = require('ReactDOMComponent');
|
||||
|
||||
var mergeInto = require('mergeInto');
|
||||
var mapObject = require('mapObject');
|
||||
|
||||
/**
|
||||
* Creates a new React class that is idempotent and capable of containing other
|
||||
* React components. It accepts event listeners and DOM properties that are
|
||||
* valid according to `DOMProperty`.
|
||||
* Create a factory that creates HTML tag descriptors.
|
||||
*
|
||||
* - Event listeners: `onClick`, `onMouseDown`, etc.
|
||||
* - DOM properties: `className`, `name`, `title`, etc.
|
||||
*
|
||||
* The `style` property functions differently from the DOM API. It accepts an
|
||||
* object mapping of style properties to values.
|
||||
*
|
||||
* @param {boolean} omitClose True if the close tag should be omitted.
|
||||
* @param {string} tag Tag name (e.g. `div`).
|
||||
* @private
|
||||
*/
|
||||
function createDOMComponentClass(omitClose, tag) {
|
||||
var Constructor = function(props) {
|
||||
// This constructor and it's argument is currently used by mocks.
|
||||
};
|
||||
Constructor.prototype = new ReactDOMComponent(tag, omitClose);
|
||||
Constructor.prototype.constructor = Constructor;
|
||||
Constructor.displayName = tag;
|
||||
|
||||
function createDOMFactory(tag) {
|
||||
if (__DEV__) {
|
||||
return ReactLegacyDescriptor.wrapFactory(
|
||||
ReactDescriptorValidator.createFactory(Constructor)
|
||||
ReactDescriptorValidator.createFactory(tag)
|
||||
);
|
||||
}
|
||||
return ReactLegacyDescriptor.wrapFactory(
|
||||
ReactDescriptor.createFactory(Constructor)
|
||||
ReactDescriptor.createFactory(tag)
|
||||
);
|
||||
}
|
||||
|
||||
@@ -67,145 +49,138 @@ function createDOMComponentClass(omitClose, tag) {
|
||||
* @public
|
||||
*/
|
||||
var ReactDOM = mapObject({
|
||||
a: false,
|
||||
abbr: false,
|
||||
address: false,
|
||||
area: true,
|
||||
article: false,
|
||||
aside: false,
|
||||
audio: false,
|
||||
b: false,
|
||||
base: true,
|
||||
bdi: false,
|
||||
bdo: false,
|
||||
big: false,
|
||||
blockquote: false,
|
||||
body: false,
|
||||
br: true,
|
||||
button: false,
|
||||
canvas: false,
|
||||
caption: false,
|
||||
cite: false,
|
||||
code: false,
|
||||
col: true,
|
||||
colgroup: false,
|
||||
data: false,
|
||||
datalist: false,
|
||||
dd: false,
|
||||
del: false,
|
||||
details: false,
|
||||
dfn: false,
|
||||
dialog: false,
|
||||
div: false,
|
||||
dl: false,
|
||||
dt: false,
|
||||
em: false,
|
||||
embed: true,
|
||||
fieldset: false,
|
||||
figcaption: false,
|
||||
figure: false,
|
||||
footer: false,
|
||||
form: false, // NOTE: Injected, see `ReactDOMForm`.
|
||||
h1: false,
|
||||
h2: false,
|
||||
h3: false,
|
||||
h4: false,
|
||||
h5: false,
|
||||
h6: false,
|
||||
head: false,
|
||||
header: false,
|
||||
hr: true,
|
||||
html: false,
|
||||
i: false,
|
||||
iframe: false,
|
||||
img: true,
|
||||
input: true,
|
||||
ins: false,
|
||||
kbd: false,
|
||||
keygen: true,
|
||||
label: false,
|
||||
legend: false,
|
||||
li: false,
|
||||
link: true,
|
||||
main: false,
|
||||
map: false,
|
||||
mark: false,
|
||||
menu: false,
|
||||
menuitem: false, // NOTE: Close tag should be omitted, but causes problems.
|
||||
meta: true,
|
||||
meter: false,
|
||||
nav: false,
|
||||
noscript: false,
|
||||
object: false,
|
||||
ol: false,
|
||||
optgroup: false,
|
||||
option: false,
|
||||
output: false,
|
||||
p: false,
|
||||
param: true,
|
||||
picture: false,
|
||||
pre: false,
|
||||
progress: false,
|
||||
q: false,
|
||||
rp: false,
|
||||
rt: false,
|
||||
ruby: false,
|
||||
s: false,
|
||||
samp: false,
|
||||
script: false,
|
||||
section: false,
|
||||
select: false,
|
||||
small: false,
|
||||
source: true,
|
||||
span: false,
|
||||
strong: false,
|
||||
style: false,
|
||||
sub: false,
|
||||
summary: false,
|
||||
sup: false,
|
||||
table: false,
|
||||
tbody: false,
|
||||
td: false,
|
||||
textarea: false, // NOTE: Injected, see `ReactDOMTextarea`.
|
||||
tfoot: false,
|
||||
th: false,
|
||||
thead: false,
|
||||
time: false,
|
||||
title: false,
|
||||
tr: false,
|
||||
track: true,
|
||||
u: false,
|
||||
ul: false,
|
||||
'var': false,
|
||||
video: false,
|
||||
wbr: true,
|
||||
a: 'a',
|
||||
abbr: 'abbr',
|
||||
address: 'address',
|
||||
area: 'area',
|
||||
article: 'article',
|
||||
aside: 'aside',
|
||||
audio: 'audio',
|
||||
b: 'b',
|
||||
base: 'base',
|
||||
bdi: 'bdi',
|
||||
bdo: 'bdo',
|
||||
big: 'big',
|
||||
blockquote: 'blockquote',
|
||||
body: 'body',
|
||||
br: 'br',
|
||||
button: 'button',
|
||||
canvas: 'canvas',
|
||||
caption: 'caption',
|
||||
cite: 'cite',
|
||||
code: 'code',
|
||||
col: 'col',
|
||||
colgroup: 'colgroup',
|
||||
data: 'data',
|
||||
datalist: 'datalist',
|
||||
dd: 'dd',
|
||||
del: 'del',
|
||||
details: 'details',
|
||||
dfn: 'dfn',
|
||||
dialog: 'dialog',
|
||||
div: 'div',
|
||||
dl: 'dl',
|
||||
dt: 'dt',
|
||||
em: 'em',
|
||||
embed: 'embed',
|
||||
fieldset: 'fieldset',
|
||||
figcaption: 'figcaption',
|
||||
figure: 'figure',
|
||||
footer: 'footer',
|
||||
form: 'form',
|
||||
h1: 'h1',
|
||||
h2: 'h2',
|
||||
h3: 'h3',
|
||||
h4: 'h4',
|
||||
h5: 'h5',
|
||||
h6: 'h6',
|
||||
head: 'head',
|
||||
header: 'header',
|
||||
hr: 'hr',
|
||||
html: 'html',
|
||||
i: 'i',
|
||||
iframe: 'iframe',
|
||||
img: 'img',
|
||||
input: 'input',
|
||||
ins: 'ins',
|
||||
kbd: 'kbd',
|
||||
keygen: 'keygen',
|
||||
label: 'label',
|
||||
legend: 'legend',
|
||||
li: 'li',
|
||||
link: 'link',
|
||||
main: 'main',
|
||||
map: 'map',
|
||||
mark: 'mark',
|
||||
menu: 'menu',
|
||||
menuitem: 'menuitem',
|
||||
meta: 'meta',
|
||||
meter: 'meter',
|
||||
nav: 'nav',
|
||||
noscript: 'noscript',
|
||||
object: 'object',
|
||||
ol: 'ol',
|
||||
optgroup: 'optgroup',
|
||||
option: 'option',
|
||||
output: 'output',
|
||||
p: 'p',
|
||||
param: 'param',
|
||||
picture: 'picture',
|
||||
pre: 'pre',
|
||||
progress: 'progress',
|
||||
q: 'q',
|
||||
rp: 'rp',
|
||||
rt: 'rt',
|
||||
ruby: 'ruby',
|
||||
s: 's',
|
||||
samp: 'samp',
|
||||
script: 'script',
|
||||
section: 'section',
|
||||
select: 'select',
|
||||
small: 'small',
|
||||
source: 'source',
|
||||
span: 'span',
|
||||
strong: 'strong',
|
||||
style: 'style',
|
||||
sub: 'sub',
|
||||
summary: 'summary',
|
||||
sup: 'sup',
|
||||
table: 'table',
|
||||
tbody: 'tbody',
|
||||
td: 'td',
|
||||
textarea: 'textarea',
|
||||
tfoot: 'tfoot',
|
||||
th: 'th',
|
||||
thead: 'thead',
|
||||
time: 'time',
|
||||
title: 'title',
|
||||
tr: 'tr',
|
||||
track: 'track',
|
||||
u: 'u',
|
||||
ul: 'ul',
|
||||
'var': 'var',
|
||||
video: 'video',
|
||||
wbr: 'wbr',
|
||||
|
||||
// SVG
|
||||
circle: false,
|
||||
defs: false,
|
||||
ellipse: false,
|
||||
g: false,
|
||||
line: false,
|
||||
linearGradient: false,
|
||||
mask: false,
|
||||
path: false,
|
||||
pattern: false,
|
||||
polygon: false,
|
||||
polyline: false,
|
||||
radialGradient: false,
|
||||
rect: false,
|
||||
stop: false,
|
||||
svg: false,
|
||||
text: false,
|
||||
tspan: false
|
||||
}, createDOMComponentClass);
|
||||
circle: 'circle',
|
||||
defs: 'defs',
|
||||
ellipse: 'ellipse',
|
||||
g: 'g',
|
||||
line: 'line',
|
||||
linearGradient: 'linearGradient',
|
||||
mask: 'mask',
|
||||
path: 'path',
|
||||
pattern: 'pattern',
|
||||
polygon: 'polygon',
|
||||
polyline: 'polyline',
|
||||
radialGradient: 'radialGradient',
|
||||
rect: 'rect',
|
||||
stop: 'stop',
|
||||
svg: 'svg',
|
||||
text: 'text',
|
||||
tspan: 'tspan'
|
||||
|
||||
var injection = {
|
||||
injectComponentClasses: function(componentClasses) {
|
||||
mergeInto(ReactDOM, componentClasses);
|
||||
}
|
||||
};
|
||||
|
||||
ReactDOM.injection = injection;
|
||||
}, createDOMFactory);
|
||||
|
||||
module.exports = ReactDOM;
|
||||
|
||||
@@ -49,7 +49,7 @@ function renderComponentToString(component) {
|
||||
transaction = ReactServerRenderingTransaction.getPooled(false);
|
||||
|
||||
return transaction.perform(function() {
|
||||
var componentInstance = instantiateReactComponent(component);
|
||||
var componentInstance = instantiateReactComponent(component, null);
|
||||
var markup = componentInstance.mountComponent(id, transaction, 0);
|
||||
return ReactMarkupChecksum.addChecksumToMarkup(markup);
|
||||
}, null);
|
||||
@@ -75,7 +75,7 @@ function renderComponentToStaticMarkup(component) {
|
||||
transaction = ReactServerRenderingTransaction.getPooled(true);
|
||||
|
||||
return transaction.perform(function() {
|
||||
var componentInstance = instantiateReactComponent(component);
|
||||
var componentInstance = instantiateReactComponent(component, null);
|
||||
return componentInstance.mountComponent(id, transaction, 0);
|
||||
}, null);
|
||||
} finally {
|
||||
|
||||
@@ -101,18 +101,51 @@ function putListener(id, registrationName, listener, transaction) {
|
||||
);
|
||||
}
|
||||
|
||||
// For HTML, certain tags should omit their close tag. We keep a whitelist for
|
||||
// those special cased tags.
|
||||
|
||||
var omittedCloseTags = {
|
||||
'area': true,
|
||||
'base': true,
|
||||
'br': true,
|
||||
'col': true,
|
||||
'embed': true,
|
||||
'hr': true,
|
||||
'img': true,
|
||||
'input': true,
|
||||
'keygen': true,
|
||||
'link': true,
|
||||
'meta': true,
|
||||
'param': true,
|
||||
'source': true,
|
||||
'track': true,
|
||||
'wbr': true
|
||||
// NOTE: menuitem's close tag should be omitted, but that causes problems.
|
||||
};
|
||||
|
||||
/**
|
||||
* Creates a new React class that is idempotent and capable of containing other
|
||||
* React components. It accepts event listeners and DOM properties that are
|
||||
* valid according to `DOMProperty`.
|
||||
*
|
||||
* - Event listeners: `onClick`, `onMouseDown`, etc.
|
||||
* - DOM properties: `className`, `name`, `title`, etc.
|
||||
*
|
||||
* The `style` property functions differently from the DOM API. It accepts an
|
||||
* object mapping of style properties to values.
|
||||
*
|
||||
* @constructor ReactDOMComponent
|
||||
* @extends ReactComponent
|
||||
* @extends ReactMultiChild
|
||||
*/
|
||||
function ReactDOMComponent(tag, omitClose) {
|
||||
this._tagOpen = '<' + tag;
|
||||
this._tagClose = omitClose ? '' : '</' + tag + '>';
|
||||
function ReactDOMComponent(tag) {
|
||||
// TODO: DANGEROUS this tag should be sanitized.
|
||||
this._tag = tag;
|
||||
this.tagName = tag.toUpperCase();
|
||||
}
|
||||
|
||||
ReactDOMComponent.displayName = 'ReactDOMComponent';
|
||||
|
||||
ReactDOMComponent.Mixin = {
|
||||
|
||||
/**
|
||||
@@ -136,10 +169,11 @@ ReactDOMComponent.Mixin = {
|
||||
mountDepth
|
||||
);
|
||||
assertValidProps(this.props);
|
||||
var closeTag = omittedCloseTags[this._tag] ? '' : '</' + this._tag + '>';
|
||||
return (
|
||||
this._createOpenTagMarkupAndPutListeners(transaction) +
|
||||
this._createContentMarkup(transaction) +
|
||||
this._tagClose
|
||||
closeTag
|
||||
);
|
||||
}
|
||||
),
|
||||
@@ -158,7 +192,7 @@ ReactDOMComponent.Mixin = {
|
||||
*/
|
||||
_createOpenTagMarkupAndPutListeners: function(transaction) {
|
||||
var props = this.props;
|
||||
var ret = this._tagOpen;
|
||||
var ret = '<' + this._tag;
|
||||
|
||||
for (var propKey in props) {
|
||||
if (!props.hasOwnProperty(propKey)) {
|
||||
|
||||
@@ -31,7 +31,7 @@ var ReactBrowserComponentMixin = require('ReactBrowserComponentMixin');
|
||||
var ReactComponentBrowserEnvironment =
|
||||
require('ReactComponentBrowserEnvironment');
|
||||
var ReactDefaultBatchingStrategy = require('ReactDefaultBatchingStrategy');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactDOMComponent = require('ReactDOMComponent');
|
||||
var ReactDOMButton = require('ReactDOMButton');
|
||||
var ReactDOMForm = require('ReactDOMForm');
|
||||
var ReactDOMImg = require('ReactDOMImg');
|
||||
@@ -76,18 +76,22 @@ function inject() {
|
||||
BeforeInputEventPlugin: BeforeInputEventPlugin
|
||||
});
|
||||
|
||||
ReactInjection.DOM.injectComponentClasses({
|
||||
button: ReactDOMButton,
|
||||
form: ReactDOMForm,
|
||||
img: ReactDOMImg,
|
||||
input: ReactDOMInput,
|
||||
option: ReactDOMOption,
|
||||
select: ReactDOMSelect,
|
||||
textarea: ReactDOMTextarea,
|
||||
ReactInjection.NativeComponent.injectGenericComponentClass(
|
||||
ReactDOMComponent
|
||||
);
|
||||
|
||||
html: createFullPageComponent(ReactDOM.html),
|
||||
head: createFullPageComponent(ReactDOM.head),
|
||||
body: createFullPageComponent(ReactDOM.body)
|
||||
ReactInjection.NativeComponent.injectComponentClasses({
|
||||
'button': ReactDOMButton,
|
||||
'form': ReactDOMForm,
|
||||
'img': ReactDOMImg,
|
||||
'input': ReactDOMInput,
|
||||
'option': ReactDOMOption,
|
||||
'select': ReactDOMSelect,
|
||||
'textarea': ReactDOMTextarea,
|
||||
|
||||
'html': createFullPageComponent('html'),
|
||||
'head': createFullPageComponent('head'),
|
||||
'body': createFullPageComponent('body')
|
||||
});
|
||||
|
||||
// This needs to happen after createFullPageComponent() otherwise the mixin
|
||||
@@ -97,7 +101,7 @@ function inject() {
|
||||
ReactInjection.DOMProperty.injectDOMPropertyConfig(HTMLDOMPropertyConfig);
|
||||
ReactInjection.DOMProperty.injectDOMPropertyConfig(SVGDOMPropertyConfig);
|
||||
|
||||
ReactInjection.EmptyComponent.injectEmptyComponent(ReactDOM.noscript);
|
||||
ReactInjection.EmptyComponent.injectEmptyComponent('noscript');
|
||||
|
||||
ReactInjection.Updates.injectReconcileTransaction(
|
||||
ReactComponentBrowserEnvironment.ReactReconcileTransaction
|
||||
|
||||
@@ -22,9 +22,9 @@ var DOMProperty = require('DOMProperty');
|
||||
var EventPluginHub = require('EventPluginHub');
|
||||
var ReactComponent = require('ReactComponent');
|
||||
var ReactCompositeComponent = require('ReactCompositeComponent');
|
||||
var ReactDOM = require('ReactDOM');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
var ReactBrowserEventEmitter = require('ReactBrowserEventEmitter');
|
||||
var ReactNativeComponent = require('ReactNativeComponent');
|
||||
var ReactPerf = require('ReactPerf');
|
||||
var ReactRootIndex = require('ReactRootIndex');
|
||||
var ReactUpdates = require('ReactUpdates');
|
||||
@@ -35,8 +35,8 @@ var ReactInjection = {
|
||||
DOMProperty: DOMProperty.injection,
|
||||
EmptyComponent: ReactEmptyComponent.injection,
|
||||
EventPluginHub: EventPluginHub.injection,
|
||||
DOM: ReactDOM.injection,
|
||||
EventEmitter: ReactBrowserEventEmitter.injection,
|
||||
NativeComponent: ReactNativeComponent.injection,
|
||||
Perf: ReactPerf.injection,
|
||||
RootIndex: ReactRootIndex.injection,
|
||||
Updates: ReactUpdates.injection
|
||||
|
||||
@@ -307,7 +307,7 @@ var ReactMount = {
|
||||
'componentDidUpdate.'
|
||||
);
|
||||
|
||||
var componentInstance = instantiateReactComponent(nextComponent);
|
||||
var componentInstance = instantiateReactComponent(nextComponent, null);
|
||||
var reactRootID = ReactMount._registerComponent(
|
||||
componentInstance,
|
||||
container
|
||||
|
||||
@@ -33,16 +33,14 @@ var invariant = require('invariant');
|
||||
* take advantage of React's reconciliation for styling and <title>
|
||||
* management. So we just document it and throw in dangerous cases.
|
||||
*
|
||||
* @param {function} componentClass convenience constructor to wrap
|
||||
* @param {string} tag The tag to wrap
|
||||
* @return {function} convenience constructor of new component
|
||||
*/
|
||||
function createFullPageComponent(componentClass) {
|
||||
var elementFactory = ReactDescriptor.createFactory(componentClass.type);
|
||||
function createFullPageComponent(tag) {
|
||||
var elementFactory = ReactDescriptor.createFactory(tag);
|
||||
|
||||
var FullPageComponent = ReactCompositeComponent.createClass({
|
||||
displayName: 'ReactFullPageComponent' + (
|
||||
componentClass.type.displayName || ''
|
||||
),
|
||||
displayName: 'ReactFullPageComponent' + tag,
|
||||
|
||||
componentWillUnmount: function() {
|
||||
invariant(
|
||||
|
||||
@@ -793,7 +793,8 @@ var ReactCompositeComponentMixin = {
|
||||
}
|
||||
|
||||
this._renderedComponent = instantiateReactComponent(
|
||||
this._renderValidatedComponent()
|
||||
this._renderValidatedComponent(),
|
||||
this._descriptor.type // The wrapping type
|
||||
);
|
||||
|
||||
// Done with mounting, `setState` will now trigger UI changes.
|
||||
@@ -1185,7 +1186,10 @@ var ReactCompositeComponentMixin = {
|
||||
var thisID = this._rootNodeID;
|
||||
var prevComponentID = prevComponentInstance._rootNodeID;
|
||||
prevComponentInstance.unmountComponent();
|
||||
this._renderedComponent = instantiateReactComponent(nextDescriptor);
|
||||
this._renderedComponent = instantiateReactComponent(
|
||||
nextDescriptor,
|
||||
this._descriptor.type
|
||||
);
|
||||
var nextMarkup = this._renderedComponent.mountComponent(
|
||||
thisID,
|
||||
transaction,
|
||||
|
||||
@@ -222,10 +222,13 @@ ReactDescriptor.cloneAndReplaceProps = function(oldDescriptor, newProps) {
|
||||
* @public
|
||||
*/
|
||||
ReactDescriptor.isValidFactory = function(factory) {
|
||||
return typeof factory === 'function' &&
|
||||
typeof factory.type === 'function' &&
|
||||
typeof factory.type.prototype.mountComponent === 'function' &&
|
||||
typeof factory.type.prototype.receiveComponent === 'function';
|
||||
return typeof factory === 'function' && (
|
||||
typeof factory.type === 'string' || (
|
||||
typeof factory.type === 'function' &&
|
||||
typeof factory.type.prototype.mountComponent === 'function' &&
|
||||
typeof factory.type.prototype.receiveComponent === 'function'
|
||||
)
|
||||
);
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -29,7 +29,7 @@ var nullComponentIdsRegistry = {};
|
||||
|
||||
var ReactEmptyComponentInjection = {
|
||||
injectEmptyComponent: function(emptyComponent) {
|
||||
component = ReactDescriptor.createFactory(emptyComponent.type);
|
||||
component = ReactDescriptor.createFactory(emptyComponent);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -195,7 +195,7 @@ var ReactMultiChild = {
|
||||
if (children.hasOwnProperty(name)) {
|
||||
// The rendered children must be turned into instances as they're
|
||||
// mounted.
|
||||
var childInstance = instantiateReactComponent(child);
|
||||
var childInstance = instantiateReactComponent(child, null);
|
||||
children[name] = childInstance;
|
||||
// Inlined for performance, see `ReactInstanceHandles.createReactID`.
|
||||
var rootID = this._rootNodeID + name;
|
||||
@@ -300,7 +300,10 @@ var ReactMultiChild = {
|
||||
this._unmountChildByName(prevChild, name);
|
||||
}
|
||||
// The child must be instantiated before it's mounted.
|
||||
var nextChildInstance = instantiateReactComponent(nextDescriptor);
|
||||
var nextChildInstance = instantiateReactComponent(
|
||||
nextDescriptor,
|
||||
null
|
||||
);
|
||||
this._mountChildByNameAtIndex(
|
||||
nextChildInstance, name, nextIndex, transaction
|
||||
);
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
/**
|
||||
* Copyright 2014 Facebook, Inc.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*
|
||||
* @providesModule ReactNativeComponent
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var invariant = require('invariant');
|
||||
var mergeInto = require('mergeInto');
|
||||
|
||||
var genericComponentClass = null;
|
||||
// This registry keeps track of wrapper classes around native tags
|
||||
var tagToComponentClass = {};
|
||||
|
||||
var ReactNativeComponentInjection = {
|
||||
// This accepts a class that receives the tag string. This is a catch all
|
||||
// that can render any kind of tag.
|
||||
injectGenericComponentClass: function(componentClass) {
|
||||
genericComponentClass = componentClass;
|
||||
},
|
||||
// This accepts a keyed object with classes as values. Each key represents a
|
||||
// tag. That particular tag will use this class instead of the generic one.
|
||||
injectComponentClasses: function(componentClasses) {
|
||||
mergeInto(tagToComponentClass, componentClasses);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create an internal class for a specific tag.
|
||||
*
|
||||
* @param {string} tag The tag for which to create an internal instance.
|
||||
* @param {any} props The props passed to the instance constructor.
|
||||
* @return {ReactComponent} component The injected empty component.
|
||||
*/
|
||||
function createInstanceForTag(tag, props, parentType) {
|
||||
var componentClass = tagToComponentClass[tag];
|
||||
if (componentClass == null) {
|
||||
invariant(
|
||||
genericComponentClass,
|
||||
'There is no registered component for the tag %s',
|
||||
tag
|
||||
);
|
||||
return new genericComponentClass(tag, props);
|
||||
}
|
||||
if (parentType === tag) {
|
||||
// Avoid recursion
|
||||
invariant(
|
||||
genericComponentClass,
|
||||
'There is no registered component for the tag %s',
|
||||
tag
|
||||
);
|
||||
return new genericComponentClass(tag, props);
|
||||
}
|
||||
// Unwrap legacy factories
|
||||
return new componentClass.type(props);
|
||||
}
|
||||
|
||||
var ReactNativeComponent = {
|
||||
createInstanceForTag: createInstanceForTag,
|
||||
injection: ReactNativeComponentInjection,
|
||||
};
|
||||
|
||||
module.exports = ReactNativeComponent;
|
||||
@@ -141,6 +141,8 @@ var ReactPropTransferer = {
|
||||
'don\'t own, %s. This usually means you are calling ' +
|
||||
'transferPropsTo() on a component passed in as props or children.',
|
||||
this.constructor.displayName,
|
||||
typeof descriptor.type === 'string' ?
|
||||
descriptor.type :
|
||||
descriptor.type.displayName
|
||||
);
|
||||
|
||||
|
||||
@@ -297,4 +297,10 @@ describe('ReactDescriptor', function() {
|
||||
expect(typeof Component.specialType.isRequired).toBe("function");
|
||||
});
|
||||
|
||||
it('allows a DOM descriptor to be used with a string', function() {
|
||||
var descriptor = React.createDescriptor('div', { className: 'foo' });
|
||||
var instance = ReactTestUtils.renderIntoDocument(descriptor);
|
||||
expect(instance.getDOMNode().tagName).toBe('DIV');
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
@@ -23,6 +23,7 @@ var warning = require('warning');
|
||||
|
||||
var ReactDescriptor = require('ReactDescriptor');
|
||||
var ReactLegacyDescriptor = require('ReactLegacyDescriptor');
|
||||
var ReactNativeComponent = require('ReactNativeComponent');
|
||||
var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
|
||||
/**
|
||||
@@ -30,10 +31,11 @@ var ReactEmptyComponent = require('ReactEmptyComponent');
|
||||
* mounted.
|
||||
*
|
||||
* @param {object} descriptor
|
||||
* @return {object} A new instance of componentDescriptor's constructor.
|
||||
* @param {*} parentCompositeType The composite type that resolved this.
|
||||
* @return {object} A new instance of the descriptor's constructor.
|
||||
* @protected
|
||||
*/
|
||||
function instantiateReactComponent(descriptor) {
|
||||
function instantiateReactComponent(descriptor, parentCompositeType) {
|
||||
var instance;
|
||||
|
||||
if (__DEV__) {
|
||||
@@ -41,8 +43,6 @@ function instantiateReactComponent(descriptor) {
|
||||
descriptor && (typeof descriptor.type === 'function' ||
|
||||
typeof descriptor.type === 'string'),
|
||||
'Only functions or strings can be mounted as React components.'
|
||||
// Not really strings yet, but as soon as I solve the cyclic dep, they
|
||||
// will be allowed here.
|
||||
);
|
||||
|
||||
// Resolve mock instances
|
||||
@@ -72,24 +72,32 @@ function instantiateReactComponent(descriptor) {
|
||||
// there is no render function on the instance. We replace the whole
|
||||
// component with an empty component instance instead.
|
||||
descriptor = ReactEmptyComponent.getEmptyComponent();
|
||||
instance = new descriptor.type(descriptor.props);
|
||||
} else {
|
||||
if (render._isMockFunction && !render._getMockImplementation()) {
|
||||
// Auto-mocked components may have a prototype with a mocked render
|
||||
// function. For those, we'll need to mock the result of the render
|
||||
// since we consider undefined to be invalid results from render.
|
||||
render.mockImplementation(
|
||||
ReactEmptyComponent.getEmptyComponent
|
||||
);
|
||||
}
|
||||
instance.construct(descriptor);
|
||||
return instance;
|
||||
} else if (render._isMockFunction && !render._getMockImplementation()) {
|
||||
// Auto-mocked components may have a prototype with a mocked render
|
||||
// function. For those, we'll need to mock the result of the render
|
||||
// since we consider undefined to be invalid results from render.
|
||||
render.mockImplementation(
|
||||
ReactEmptyComponent.getEmptyComponent
|
||||
);
|
||||
}
|
||||
instance.construct(descriptor);
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
|
||||
// Normal case for non-mocks
|
||||
instance = new descriptor.type(descriptor.props);
|
||||
// Special case string values
|
||||
if (typeof descriptor.type === 'string') {
|
||||
instance = ReactNativeComponent.createInstanceForTag(
|
||||
descriptor.type,
|
||||
descriptor.props,
|
||||
parentCompositeType
|
||||
);
|
||||
} else {
|
||||
// Normal case for non-mocks and non-strings
|
||||
instance = new descriptor.type(descriptor.props);
|
||||
}
|
||||
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
|
||||
@@ -106,7 +106,7 @@ mergeInto(reactComponentExpect.prototype, {
|
||||
|
||||
toBeComponentOfType: function(convenienceConstructor) {
|
||||
expect(
|
||||
this.instance().constructor === convenienceConstructor.type
|
||||
this.instance()._descriptor.type === convenienceConstructor.type
|
||||
).toBe(true);
|
||||
return this;
|
||||
},
|
||||
@@ -126,7 +126,7 @@ mergeInto(reactComponentExpect.prototype, {
|
||||
toBeCompositeComponentWithType: function(convenienceConstructor) {
|
||||
this.toBeCompositeComponent();
|
||||
expect(
|
||||
this.instance().constructor === convenienceConstructor.type
|
||||
this.instance()._descriptor.type === convenienceConstructor.type
|
||||
).toBe(true);
|
||||
return this;
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user