mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Don't pass the Fiber around instead use the DOM node
To avoid exposing the implementation details of fibers we'll just pass the DOM node around instead. We'll attach any additional wrapper state on it. We don't have to do it that way. We can also just invert the relationship and put the node in the wrapper state. I'll probably just get rid of the wrapper object and just put them as expandos on the DOM.
This commit is contained in:
@@ -14,8 +14,6 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
var CSSPropertyOperations = require('CSSPropertyOperations');
|
||||
var DOMNamespaces = require('DOMNamespaces');
|
||||
var DOMProperty = require('DOMProperty');
|
||||
@@ -39,7 +37,6 @@ var inputValueTracking = require('inputValueTracking');
|
||||
var warning = require('warning');
|
||||
var didWarnShadyDOM = false;
|
||||
|
||||
var getNode = ReactDOMComponentTree.getNodeFromInstance;
|
||||
var listenTo = ReactBrowserEventEmitter.listenTo;
|
||||
var registrationNameModules = EventPluginRegistry.registrationNameModules;
|
||||
|
||||
@@ -162,7 +159,7 @@ var mediaEvents = {
|
||||
topWaiting: 'waiting',
|
||||
};
|
||||
|
||||
function trapClickOnNonInteractiveElement(inst) {
|
||||
function trapClickOnNonInteractiveElement(node : any) {
|
||||
// Mobile Safari does not fire properly bubble click events on
|
||||
// non-interactive elements, which means delegated click listeners do not
|
||||
// fire. The workaround for this bug involves attaching an empty click
|
||||
@@ -172,20 +169,16 @@ function trapClickOnNonInteractiveElement(inst) {
|
||||
// bookkeeping for it. Not sure if we need to clear it when the listener is
|
||||
// removed.
|
||||
// TODO: Only do this for the relevant Safaris maybe?
|
||||
var node = getNode(inst);
|
||||
node.onclick = emptyFunction;
|
||||
}
|
||||
|
||||
function trapBubbledEventsLocal(inst, tag) {
|
||||
function trapBubbledEventsLocal(node : Element, tag : string) {
|
||||
// If a component renders to null or if another component fatals and causes
|
||||
// the state of the tree to be corrupted, `node` here can be null.
|
||||
var node = getNode(inst);
|
||||
invariant(
|
||||
node,
|
||||
'trapBubbledEvent(...): Requires node to be rendered.'
|
||||
);
|
||||
|
||||
// TODO: Make sure that we check isMounted before firing any of these events.
|
||||
// TODO: Inline these below since we're calling this from an equivalent
|
||||
// switch statement.
|
||||
switch (tag) {
|
||||
case 'iframe':
|
||||
case 'object':
|
||||
@@ -307,12 +300,12 @@ function isCustomComponent(tagName, props) {
|
||||
*
|
||||
* TODO: Benchmark whether checking for changed values in memory actually
|
||||
* improves performance (especially statically positioned elements).
|
||||
* TODO: Benchmark the effects of putting workInProgress at the top since 99% of props
|
||||
* TODO: Benchmark the effects of putting this at the top since 99% of props
|
||||
* do not change for a given reconciliation.
|
||||
* TODO: Benchmark areas that can be improved with caching.
|
||||
*/
|
||||
function updateDOMProperties(
|
||||
workInProgress : Fiber,
|
||||
domElement : Element,
|
||||
rootContainerElement : Element,
|
||||
lastProps : null | Object,
|
||||
nextProps : Object,
|
||||
@@ -347,13 +340,13 @@ function updateDOMProperties(
|
||||
// Do nothing for deleted listeners.
|
||||
} else if (wasCustomComponentTag) {
|
||||
DOMPropertyOperations.deleteValueForAttribute(
|
||||
getNode(workInProgress),
|
||||
domElement,
|
||||
propKey
|
||||
);
|
||||
} else if (
|
||||
DOMProperty.properties[propKey] ||
|
||||
DOMProperty.isCustomAttribute(propKey)) {
|
||||
DOMPropertyOperations.deleteValueForProperty(getNode(workInProgress), propKey);
|
||||
DOMPropertyOperations.deleteValueForProperty(domElement, propKey);
|
||||
}
|
||||
}
|
||||
for (propKey in nextProps) {
|
||||
@@ -400,10 +393,10 @@ function updateDOMProperties(
|
||||
if (nextHtml) {
|
||||
if (lastHtml) {
|
||||
if (lastHtml !== nextHtml) {
|
||||
setInnerHTML(getNode(workInProgress), '' + nextHtml);
|
||||
setInnerHTML(domElement, '' + nextHtml);
|
||||
}
|
||||
} else {
|
||||
setInnerHTML(getNode(workInProgress), nextHtml);
|
||||
setInnerHTML(domElement, nextHtml);
|
||||
}
|
||||
} else {
|
||||
// TODO: It might be too late to clear this if we have children
|
||||
@@ -411,9 +404,9 @@ function updateDOMProperties(
|
||||
}
|
||||
} else if (propKey === CHILDREN) {
|
||||
if (typeof nextProp === 'string') {
|
||||
setTextContent(getNode(workInProgress), nextProp);
|
||||
setTextContent(domElement, nextProp);
|
||||
} else if (typeof nextProp === 'number') {
|
||||
setTextContent(getNode(workInProgress), '' + nextProp);
|
||||
setTextContent(domElement, '' + nextProp);
|
||||
}
|
||||
} else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
|
||||
// Noop
|
||||
@@ -423,29 +416,28 @@ function updateDOMProperties(
|
||||
}
|
||||
} else if (isCustomComponentTag) {
|
||||
DOMPropertyOperations.setValueForAttribute(
|
||||
getNode(workInProgress),
|
||||
domElement,
|
||||
propKey,
|
||||
nextProp
|
||||
);
|
||||
} else if (
|
||||
DOMProperty.properties[propKey] ||
|
||||
DOMProperty.isCustomAttribute(propKey)) {
|
||||
var node = getNode(workInProgress);
|
||||
// If we're updating to null or undefined, we should remove the property
|
||||
// from the DOM node instead of inadvertently setting to a string. This
|
||||
// brings us in line with the same behavior we have on initial render.
|
||||
if (nextProp != null) {
|
||||
DOMPropertyOperations.setValueForProperty(node, propKey, nextProp);
|
||||
DOMPropertyOperations.setValueForProperty(domElement, propKey, nextProp);
|
||||
} else {
|
||||
DOMPropertyOperations.deleteValueForProperty(node, propKey);
|
||||
DOMPropertyOperations.deleteValueForProperty(domElement, propKey);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (styleUpdates) {
|
||||
CSSPropertyOperations.setValueForStyles(
|
||||
getNode(workInProgress),
|
||||
domElement,
|
||||
styleUpdates,
|
||||
workInProgress
|
||||
null // TODO: Change CSSPropertyOperations to use getCurrentOwnerName.
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -459,7 +451,7 @@ var ReactDOMFiberComponent = {
|
||||
},
|
||||
|
||||
mountComponent: function(
|
||||
workInProgress : Fiber,
|
||||
internalInstanceHandle : Object,
|
||||
tag : string,
|
||||
rootContainerElement : Element,
|
||||
props : Object,
|
||||
@@ -469,53 +461,6 @@ var ReactDOMFiberComponent = {
|
||||
// validateDangerousTag(tag);
|
||||
// tag.toLowerCase(); Do we need to apply lower case only on non-custom elements?
|
||||
|
||||
switch (tag) {
|
||||
case 'audio':
|
||||
case 'form':
|
||||
case 'iframe':
|
||||
case 'img':
|
||||
case 'link':
|
||||
case 'object':
|
||||
case 'source':
|
||||
case 'video':
|
||||
trapBubbledEventsLocal(workInProgress);
|
||||
break;
|
||||
case 'input':
|
||||
ReactDOMFiberInput.mountWrapper(workInProgress, props);
|
||||
props = ReactDOMFiberInput.getHostProps(workInProgress, props);
|
||||
// TODO: Make sure we check if this is still unmounted or do any clean
|
||||
// up necessary since we never stop tracking anymore.
|
||||
inputValueTracking.track(workInProgress);
|
||||
trapBubbledEventsLocal(workInProgress);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
case 'option':
|
||||
ReactDOMFiberOption.mountWrapper(workInProgress, props);
|
||||
props = ReactDOMFiberOption.getHostProps(workInProgress, props);
|
||||
break;
|
||||
case 'select':
|
||||
ReactDOMFiberSelect.mountWrapper(workInProgress, props);
|
||||
props = ReactDOMFiberSelect.getHostProps(workInProgress, props);
|
||||
trapBubbledEventsLocal(workInProgress);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.mountWrapper(workInProgress, props);
|
||||
props = ReactDOMFiberTextarea.getHostProps(workInProgress, props);
|
||||
inputValueTracking.track(workInProgress);
|
||||
trapBubbledEventsLocal(workInProgress);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
}
|
||||
|
||||
assertValidProps(tag, props);
|
||||
|
||||
// We create tags in the namespace of their parent container, except HTML
|
||||
// tags get no namespace.
|
||||
var namespaceURI = rootContainerElement.namespaceURI;
|
||||
@@ -534,7 +479,7 @@ var ReactDOMFiberComponent = {
|
||||
}
|
||||
|
||||
var ownerDocument = rootContainerElement.ownerDocument;
|
||||
var el;
|
||||
var domElement : Element;
|
||||
if (namespaceURI === DOMNamespaces.html) {
|
||||
if (tag === 'script') {
|
||||
// Create the script via .innerHTML so its "parser-inserted" flag is
|
||||
@@ -543,24 +488,24 @@ var ReactDOMFiberComponent = {
|
||||
div.innerHTML = '<script></script>';
|
||||
// This is guaranteed to yield a script element.
|
||||
var firstChild = ((div.firstChild : any) : Element);
|
||||
el = div.removeChild(firstChild);
|
||||
domElement = div.removeChild(firstChild);
|
||||
} else if (props.is) {
|
||||
el = ownerDocument.createElement(tag, props.is);
|
||||
domElement = ownerDocument.createElement(tag, props.is);
|
||||
} else {
|
||||
// Separate else branch instead of using `props.is || undefined` above becuase of a Firefox bug.
|
||||
// See discussion in https://github.com/facebook/react/pull/6896
|
||||
// and discussion in https://bugzilla.mozilla.org/show_bug.cgi?id=1276240
|
||||
el = ownerDocument.createElement(tag);
|
||||
domElement = ownerDocument.createElement(tag);
|
||||
}
|
||||
} else {
|
||||
el = ownerDocument.createElementNS(
|
||||
domElement = ownerDocument.createElementNS(
|
||||
namespaceURI,
|
||||
tag
|
||||
);
|
||||
}
|
||||
var isCustomComponentTag = isCustomComponent(tag, props);
|
||||
if (__DEV__) {
|
||||
if (isCustomComponentTag && !didWarnShadyDOM && el.shadyRoot) {
|
||||
if (isCustomComponentTag && !didWarnShadyDOM && domElement.shadyRoot) {
|
||||
warning(
|
||||
false,
|
||||
'%s is using shady DOM. Using shady DOM with React can ' +
|
||||
@@ -570,9 +515,57 @@ var ReactDOMFiberComponent = {
|
||||
didWarnShadyDOM = true;
|
||||
}
|
||||
}
|
||||
ReactDOMComponentTree.precacheFiberNode(workInProgress, el);
|
||||
|
||||
switch (tag) {
|
||||
case 'audio':
|
||||
case 'form':
|
||||
case 'iframe':
|
||||
case 'img':
|
||||
case 'link':
|
||||
case 'object':
|
||||
case 'source':
|
||||
case 'video':
|
||||
trapBubbledEventsLocal(domElement, tag);
|
||||
break;
|
||||
case 'input':
|
||||
ReactDOMFiberInput.mountWrapper(domElement, props);
|
||||
props = ReactDOMFiberInput.getHostProps(domElement, props);
|
||||
// TODO: Make sure we check if this is still unmounted or do any clean
|
||||
// up necessary since we never stop tracking anymore.
|
||||
inputValueTracking.track(domElement);
|
||||
trapBubbledEventsLocal(domElement, tag);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
case 'option':
|
||||
ReactDOMFiberOption.mountWrapper(domElement, props);
|
||||
props = ReactDOMFiberOption.getHostProps(domElement, props);
|
||||
break;
|
||||
case 'select':
|
||||
ReactDOMFiberSelect.mountWrapper(domElement, props);
|
||||
props = ReactDOMFiberSelect.getHostProps(domElement, props);
|
||||
trapBubbledEventsLocal(domElement, tag);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.mountWrapper(domElement, props);
|
||||
props = ReactDOMFiberTextarea.getHostProps(domElement, props);
|
||||
inputValueTracking.track(domElement); // TODO
|
||||
trapBubbledEventsLocal(domElement, tag);
|
||||
// For controlled components we always need to ensure we're listening
|
||||
// to onChange. Even if there is no listener.
|
||||
ensureListeningTo(rootContainerElement, 'onChange');
|
||||
break;
|
||||
}
|
||||
|
||||
assertValidProps(tag, props);
|
||||
|
||||
ReactDOMComponentTree.precacheFiberNode(internalInstanceHandle, domElement);
|
||||
updateDOMProperties(
|
||||
workInProgress,
|
||||
domElement,
|
||||
rootContainerElement,
|
||||
null,
|
||||
props,
|
||||
@@ -580,72 +573,73 @@ var ReactDOMFiberComponent = {
|
||||
isCustomComponentTag
|
||||
);
|
||||
|
||||
// TODO: All these autoFocus won't work because the component is not in the
|
||||
// DOM yet. We need a special effect to handle this.
|
||||
switch (tag) {
|
||||
case 'input':
|
||||
ReactDOMFiberInput.postMountWrapper(workInProgress, props);
|
||||
ReactDOMFiberInput.postMountWrapper(domElement, props);
|
||||
if (props.autoFocus) {
|
||||
focusNode(getNode(workInProgress));
|
||||
focusNode(domElement);
|
||||
}
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.postMountWrapper(workInProgress, props);
|
||||
ReactDOMFiberTextarea.postMountWrapper(domElement, props);
|
||||
if (props.autoFocus) {
|
||||
focusNode(getNode(workInProgress));
|
||||
focusNode(domElement);
|
||||
}
|
||||
break;
|
||||
case 'select':
|
||||
if (props.autoFocus) {
|
||||
focusNode(getNode(workInProgress));
|
||||
focusNode(domElement);
|
||||
}
|
||||
break;
|
||||
case 'button':
|
||||
if (props.autoFocus) {
|
||||
focusNode(getNode(workInProgress));
|
||||
focusNode(domElement);
|
||||
}
|
||||
break;
|
||||
case 'option':
|
||||
ReactDOMFiberOption.postMountWrapper(workInProgress, props);
|
||||
ReactDOMFiberOption.postMountWrapper(domElement, props);
|
||||
break;
|
||||
default:
|
||||
if (typeof props.onClick === 'function') {
|
||||
trapClickOnNonInteractiveElement(workInProgress);
|
||||
trapClickOnNonInteractiveElement(domElement);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
return el;
|
||||
return domElement;
|
||||
},
|
||||
|
||||
receiveComponent: function(
|
||||
workInProgress : Fiber,
|
||||
domElement : Element,
|
||||
rootContainerElement : Element,
|
||||
tag : string,
|
||||
lastProps : Object,
|
||||
nextProps : Object,
|
||||
context : Object
|
||||
) {
|
||||
var lastProps = workInProgress.memoizedProps;
|
||||
|
||||
switch (tag) {
|
||||
case 'input':
|
||||
lastProps = ReactDOMFiberInput.getHostProps(workInProgress, lastProps);
|
||||
nextProps = ReactDOMFiberInput.getHostProps(workInProgress, nextProps);
|
||||
lastProps = ReactDOMFiberInput.getHostProps(domElement, lastProps);
|
||||
nextProps = ReactDOMFiberInput.getHostProps(domElement, nextProps);
|
||||
break;
|
||||
case 'option':
|
||||
lastProps = ReactDOMFiberOption.getHostProps(workInProgress, lastProps);
|
||||
nextProps = ReactDOMFiberOption.getHostProps(workInProgress, nextProps);
|
||||
lastProps = ReactDOMFiberOption.getHostProps(domElement, lastProps);
|
||||
nextProps = ReactDOMFiberOption.getHostProps(domElement, nextProps);
|
||||
break;
|
||||
case 'select':
|
||||
lastProps = ReactDOMFiberSelect.getHostProps(workInProgress, lastProps);
|
||||
nextProps = ReactDOMFiberSelect.getHostProps(workInProgress, nextProps);
|
||||
lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastProps);
|
||||
nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextProps);
|
||||
break;
|
||||
case 'textarea':
|
||||
lastProps = ReactDOMFiberTextarea.getHostProps(workInProgress, lastProps);
|
||||
nextProps = ReactDOMFiberTextarea.getHostProps(workInProgress, nextProps);
|
||||
lastProps = ReactDOMFiberTextarea.getHostProps(domElement, lastProps);
|
||||
nextProps = ReactDOMFiberTextarea.getHostProps(domElement, nextProps);
|
||||
break;
|
||||
default:
|
||||
if (typeof lastProps.onClick !== 'function' &&
|
||||
typeof nextProps.onClick === 'function') {
|
||||
trapClickOnNonInteractiveElement(workInProgress);
|
||||
trapClickOnNonInteractiveElement(domElement);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -654,7 +648,7 @@ var ReactDOMFiberComponent = {
|
||||
var wasCustomComponentTag = isCustomComponent(tag, lastProps);
|
||||
var isCustomComponentTag = isCustomComponent(tag, nextProps);
|
||||
updateDOMProperties(
|
||||
workInProgress,
|
||||
domElement,
|
||||
rootContainerElement,
|
||||
lastProps,
|
||||
nextProps,
|
||||
@@ -667,38 +661,33 @@ var ReactDOMFiberComponent = {
|
||||
// Update the wrapper around inputs *after* updating props. This has to
|
||||
// happen after `updateDOMProperties`. Otherwise HTML5 input validations
|
||||
// raise warnings and prevent the new value from being assigned.
|
||||
ReactDOMFiberInput.updateWrapper(workInProgress, nextProps);
|
||||
ReactDOMFiberInput.updateWrapper(domElement, nextProps);
|
||||
break;
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.updateWrapper(workInProgress, nextProps);
|
||||
ReactDOMFiberTextarea.updateWrapper(domElement, nextProps);
|
||||
break;
|
||||
case 'select':
|
||||
// <select> value update needs to occur after <option> children
|
||||
// reconciliation
|
||||
ReactDOMFiberSelect.postUpdateWrapper(workInProgress, nextProps);
|
||||
ReactDOMFiberSelect.postUpdateWrapper(domElement, nextProps);
|
||||
break;
|
||||
}
|
||||
},
|
||||
|
||||
restoreControlledState: function(finishedWork : Fiber, props : Object) {
|
||||
switch (finishedWork.type) {
|
||||
restoreControlledState: function(domElement : Element, tag : string, props : Object) {
|
||||
switch (tag) {
|
||||
case 'input':
|
||||
ReactDOMFiberInput.restoreControlledState(finishedWork, props);
|
||||
ReactDOMFiberInput.restoreControlledState(domElement, props);
|
||||
return;
|
||||
case 'textarea':
|
||||
ReactDOMFiberTextarea.restoreControlledState(finishedWork, props);
|
||||
ReactDOMFiberTextarea.restoreControlledState(domElement, props);
|
||||
return;
|
||||
case 'select':
|
||||
ReactDOMFiberSelect.restoreControlledState(finishedWork, props);
|
||||
ReactDOMFiberSelect.restoreControlledState(domElement, props);
|
||||
return;
|
||||
}
|
||||
},
|
||||
|
||||
getPublicInstance: function(fiber : Fiber) {
|
||||
// If we add wrappers, this could be something deeper.
|
||||
return fiber.stateNode;
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
module.exports = ReactDOMFiberComponent;
|
||||
|
||||
@@ -12,7 +12,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
type InputWithWrapperState = HTMLInputElement & {
|
||||
_wrapperState: {
|
||||
initialValue: ?string,
|
||||
initialChecked: ?boolean,
|
||||
controlled?: boolean
|
||||
}
|
||||
};
|
||||
|
||||
var DOMPropertyOperations = require('DOMPropertyOperations');
|
||||
var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
|
||||
@@ -49,7 +55,8 @@ function isControlled(props) {
|
||||
* See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
|
||||
*/
|
||||
var ReactDOMInput = {
|
||||
getHostProps: function(inst : Fiber, props : Object) {
|
||||
getHostProps: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : InputWithWrapperState);
|
||||
var value = props.value;
|
||||
var checked = props.checked;
|
||||
|
||||
@@ -67,14 +74,14 @@ var ReactDOMInput = {
|
||||
}, props, {
|
||||
defaultChecked: undefined,
|
||||
defaultValue: undefined,
|
||||
value: value != null ? value : inst._wrapperState.initialValue,
|
||||
checked: checked != null ? checked : inst._wrapperState.initialChecked,
|
||||
value: value != null ? value : node._wrapperState.initialValue,
|
||||
checked: checked != null ? checked : node._wrapperState.initialChecked,
|
||||
});
|
||||
|
||||
return hostProps;
|
||||
},
|
||||
|
||||
mountWrapper: function(inst : Fiber, props : Object) {
|
||||
mountWrapper: function(element : Element, props : Object) {
|
||||
if (__DEV__) {
|
||||
ReactControlledValuePropTypes.checkPropTypes(
|
||||
'input',
|
||||
@@ -121,21 +128,23 @@ var ReactDOMInput = {
|
||||
}
|
||||
|
||||
var defaultValue = props.defaultValue;
|
||||
inst._wrapperState = {
|
||||
var node = ((element : any) : InputWithWrapperState);
|
||||
node._wrapperState = {
|
||||
initialChecked: props.checked != null ? props.checked : props.defaultChecked,
|
||||
initialValue: props.value != null ? props.value : defaultValue,
|
||||
};
|
||||
|
||||
if (__DEV__) {
|
||||
inst._wrapperState.controlled = isControlled(props);
|
||||
node._wrapperState.controlled = isControlled(props);
|
||||
}
|
||||
},
|
||||
|
||||
updateWrapper: function(inst : Fiber, props : Object) {
|
||||
updateWrapper: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : InputWithWrapperState);
|
||||
if (__DEV__) {
|
||||
var controlled = isControlled(props);
|
||||
|
||||
if (!inst._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {
|
||||
if (!node._wrapperState.controlled && controlled && !didWarnUncontrolledToControlled) {
|
||||
warning(
|
||||
false,
|
||||
'%s is changing an uncontrolled input of type %s to be controlled. ' +
|
||||
@@ -147,7 +156,7 @@ var ReactDOMInput = {
|
||||
);
|
||||
didWarnUncontrolledToControlled = true;
|
||||
}
|
||||
if (inst._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {
|
||||
if (node._wrapperState.controlled && !controlled && !didWarnControlledToUncontrolled) {
|
||||
warning(
|
||||
false,
|
||||
'%s is changing a controlled input of type %s to be uncontrolled. ' +
|
||||
@@ -164,13 +173,12 @@ var ReactDOMInput = {
|
||||
var checked = props.checked;
|
||||
if (checked != null) {
|
||||
DOMPropertyOperations.setValueForProperty(
|
||||
ReactDOMComponentTree.getNodeFromInstance(inst),
|
||||
node,
|
||||
'checked',
|
||||
checked || false
|
||||
);
|
||||
}
|
||||
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
|
||||
var value = props.value;
|
||||
if (value != null) {
|
||||
|
||||
@@ -202,10 +210,8 @@ var ReactDOMInput = {
|
||||
}
|
||||
},
|
||||
|
||||
postMountWrapper: function(inst : Fiber, props : Object) {
|
||||
// This is in postMount because we need access to the DOM node, which is not
|
||||
// available until after the component has mounted.
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
|
||||
postMountWrapper: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : InputWithWrapperState);
|
||||
|
||||
// Detach value from defaultValue. We won't do anything if we're working on
|
||||
// submit or reset inputs as those values & defaultValues are linked. They
|
||||
@@ -250,20 +256,20 @@ var ReactDOMInput = {
|
||||
}
|
||||
},
|
||||
|
||||
restoreControlledState: function(inst : Fiber, props : Object) {
|
||||
ReactDOMInput.updateWrapper(inst, props);
|
||||
updateNamedCousins(inst, props);
|
||||
restoreControlledState: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : InputWithWrapperState);
|
||||
ReactDOMInput.updateWrapper(node, props);
|
||||
updateNamedCousins(node, props);
|
||||
},
|
||||
};
|
||||
|
||||
function updateNamedCousins(thisInstance, props) {
|
||||
function updateNamedCousins(rootNode, props) {
|
||||
var name = props.name;
|
||||
if (props.type === 'radio' && name != null) {
|
||||
var rootNode = ReactDOMComponentTree.getNodeFromInstance(thisInstance);
|
||||
var queryRoot = rootNode;
|
||||
var queryRoot : Element = rootNode;
|
||||
|
||||
while (queryRoot.parentNode) {
|
||||
queryRoot = queryRoot.parentNode;
|
||||
queryRoot = ((queryRoot.parentNode : any) : Element);
|
||||
}
|
||||
|
||||
// If `rootNode.form` was non-null, then we could try `form.elements`,
|
||||
@@ -277,7 +283,7 @@ function updateNamedCousins(thisInstance, props) {
|
||||
'input[name=' + JSON.stringify('' + name) + '][type="radio"]');
|
||||
|
||||
for (var i = 0; i < group.length; i++) {
|
||||
var otherNode = group[i];
|
||||
var otherNode = ((group[i] : any) : HTMLInputElement);
|
||||
if (otherNode === rootNode ||
|
||||
otherNode.form !== rootNode.form) {
|
||||
continue;
|
||||
@@ -296,7 +302,7 @@ function updateNamedCousins(thisInstance, props) {
|
||||
// If this is a controlled radio button group, forcing the input that
|
||||
// was previously checked to update will cause it to be come re-checked
|
||||
// as appropriate.
|
||||
ReactDOMInput.updateWrapper(otherInstance, otherInstance.memoizedProps);
|
||||
ReactDOMInput.updateWrapper(otherNode, otherInstance.memoizedProps);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,10 +12,7 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
|
||||
var React = require('React');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
|
||||
var warning = require('warning');
|
||||
var didWarnInvalidOptionChildren = false;
|
||||
@@ -47,7 +44,7 @@ function flattenChildren(children) {
|
||||
* Implements an <option> host component that warns when `selected` is set.
|
||||
*/
|
||||
var ReactDOMOption = {
|
||||
mountWrapper: function(inst : Fiber, props : Object) {
|
||||
mountWrapper: function(element : Element, props : Object) {
|
||||
// TODO (yungsters): Remove support for `selected` in <option>.
|
||||
if (__DEV__) {
|
||||
warning(
|
||||
@@ -58,15 +55,14 @@ var ReactDOMOption = {
|
||||
}
|
||||
},
|
||||
|
||||
postMountWrapper: function(inst : Fiber, props : Object) {
|
||||
postMountWrapper: function(element : Element, props : Object) {
|
||||
// value="" should make a value attribute (#6219)
|
||||
if (props.value != null) {
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
|
||||
node.setAttribute('value', props.value);
|
||||
element.setAttribute('value', props.value);
|
||||
}
|
||||
},
|
||||
|
||||
getHostProps: function(inst : Fiber, props : Object) {
|
||||
getHostProps: function(element : Element, props : Object) {
|
||||
var hostProps = Object.assign({children: undefined}, props);
|
||||
|
||||
var content = flattenChildren(props.children);
|
||||
|
||||
@@ -12,10 +12,14 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
type SelectWithWrapperState = HTMLSelectElement & {
|
||||
_wrapperState: {
|
||||
initialValue: ?string,
|
||||
wasMultiple: boolean
|
||||
}
|
||||
};
|
||||
|
||||
var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
|
||||
var getCurrentOwnerName = require('getCurrentOwnerName');
|
||||
var warning = require('warning');
|
||||
@@ -35,7 +39,7 @@ var valuePropNames = ['value', 'defaultValue'];
|
||||
/**
|
||||
* Validation function for `value` and `defaultValue`.
|
||||
*/
|
||||
function checkSelectPropTypes(inst, props) {
|
||||
function checkSelectPropTypes(props) {
|
||||
ReactControlledValuePropTypes.checkPropTypes(
|
||||
'select',
|
||||
props,
|
||||
@@ -68,8 +72,9 @@ function checkSelectPropTypes(inst, props) {
|
||||
}
|
||||
}
|
||||
|
||||
function updateOptions(inst : Fiber, multiple : boolean, propValue : any) {
|
||||
var options = ReactDOMComponentTree.getNodeFromInstance(inst).options;
|
||||
function updateOptions(node : HTMLSelectElement, multiple : boolean, propValue : any) {
|
||||
type IndexableHTMLOptionsCollection = HTMLOptionsCollection & { [key:number]: HTMLOptionElement };
|
||||
var options : IndexableHTMLOptionsCollection = node.options;
|
||||
|
||||
if (multiple) {
|
||||
let selectedValues = (propValue : Array<string>);
|
||||
@@ -115,19 +120,20 @@ function updateOptions(inst : Fiber, multiple : boolean, propValue : any) {
|
||||
* selected.
|
||||
*/
|
||||
var ReactDOMSelect = {
|
||||
getHostProps: function(inst : Fiber, props : Object) {
|
||||
getHostProps: function(element : Element, props : Object) {
|
||||
return Object.assign({}, props, {
|
||||
value: undefined,
|
||||
});
|
||||
},
|
||||
|
||||
mountWrapper: function(inst : Fiber, props : Object) {
|
||||
mountWrapper: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : SelectWithWrapperState);
|
||||
if (__DEV__) {
|
||||
checkSelectPropTypes(inst, props);
|
||||
checkSelectPropTypes(props);
|
||||
}
|
||||
|
||||
var value = props.value;
|
||||
inst._wrapperState = {
|
||||
node._wrapperState = {
|
||||
initialValue: value != null ? value : props.defaultValue,
|
||||
wasMultiple: Boolean(props.multiple),
|
||||
};
|
||||
@@ -149,33 +155,35 @@ var ReactDOMSelect = {
|
||||
}
|
||||
},
|
||||
|
||||
postUpdateWrapper: function(inst : Fiber, props : Object) {
|
||||
postUpdateWrapper: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : SelectWithWrapperState);
|
||||
// After the initial mount, we control selected-ness manually so don't pass
|
||||
// this value down
|
||||
inst._wrapperState.initialValue = undefined;
|
||||
node._wrapperState.initialValue = undefined;
|
||||
|
||||
var wasMultiple = inst._wrapperState.wasMultiple;
|
||||
inst._wrapperState.wasMultiple = Boolean(props.multiple);
|
||||
var wasMultiple = node._wrapperState.wasMultiple;
|
||||
node._wrapperState.wasMultiple = Boolean(props.multiple);
|
||||
|
||||
var value = props.value;
|
||||
if (value != null) {
|
||||
updateOptions(inst, Boolean(props.multiple), value);
|
||||
updateOptions(node, Boolean(props.multiple), value);
|
||||
} else if (wasMultiple !== Boolean(props.multiple)) {
|
||||
// For simplicity, reapply `defaultValue` if `multiple` is toggled.
|
||||
if (props.defaultValue != null) {
|
||||
updateOptions(inst, Boolean(props.multiple), props.defaultValue);
|
||||
updateOptions(node, Boolean(props.multiple), props.defaultValue);
|
||||
} else {
|
||||
// Revert the select back to its default unselected state.
|
||||
updateOptions(inst, Boolean(props.multiple), props.multiple ? [] : '');
|
||||
updateOptions(node, Boolean(props.multiple), props.multiple ? [] : '');
|
||||
}
|
||||
}
|
||||
},
|
||||
|
||||
restoreControlledState: function(inst : Fiber, props : Object) {
|
||||
restoreControlledState: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : SelectWithWrapperState);
|
||||
var value = props.value;
|
||||
|
||||
if (value != null) {
|
||||
updateOptions(inst, Boolean(props.multiple), value);
|
||||
updateOptions(node, Boolean(props.multiple), value);
|
||||
}
|
||||
},
|
||||
};
|
||||
|
||||
@@ -12,10 +12,13 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import type { Fiber } from 'ReactFiber';
|
||||
type TextAreaWithWrapperState = HTMLTextAreaElement & {
|
||||
_wrapperState: {
|
||||
initialValue: string,
|
||||
}
|
||||
};
|
||||
|
||||
var ReactControlledValuePropTypes = require('ReactControlledValuePropTypes');
|
||||
var ReactDOMComponentTree = require('ReactDOMComponentTree');
|
||||
|
||||
var getCurrentOwnerName = require('getCurrentOwnerName');
|
||||
var invariant = require('invariant');
|
||||
@@ -39,7 +42,8 @@ var didWarnValDefaultVal = false;
|
||||
* `defaultValue` if specified, or the children content (deprecated).
|
||||
*/
|
||||
var ReactDOMTextarea = {
|
||||
getHostProps: function(inst : Fiber, props : Object) {
|
||||
getHostProps: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : TextAreaWithWrapperState);
|
||||
invariant(
|
||||
props.dangerouslySetInnerHTML == null,
|
||||
'`dangerouslySetInnerHTML` does not make sense on <textarea>.'
|
||||
@@ -53,13 +57,14 @@ var ReactDOMTextarea = {
|
||||
var hostProps = Object.assign({}, props, {
|
||||
value: undefined,
|
||||
defaultValue: undefined,
|
||||
children: '' + inst._wrapperState.initialValue,
|
||||
children: '' + node._wrapperState.initialValue,
|
||||
});
|
||||
|
||||
return hostProps;
|
||||
},
|
||||
|
||||
mountWrapper: function(inst : Fiber, props : Object) {
|
||||
mountWrapper: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : TextAreaWithWrapperState);
|
||||
if (__DEV__) {
|
||||
ReactControlledValuePropTypes.checkPropTypes(
|
||||
'textarea',
|
||||
@@ -120,13 +125,13 @@ var ReactDOMTextarea = {
|
||||
initialValue = defaultValue;
|
||||
}
|
||||
|
||||
inst._wrapperState = {
|
||||
node._wrapperState = {
|
||||
initialValue: '' + initialValue,
|
||||
};
|
||||
},
|
||||
|
||||
updateWrapper: function(inst : Fiber, props : Object) {
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
|
||||
updateWrapper: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : TextAreaWithWrapperState);
|
||||
var value = props.value;
|
||||
if (value != null) {
|
||||
// Cast `value` to a string to ensure the value is set correctly. While
|
||||
@@ -146,24 +151,24 @@ var ReactDOMTextarea = {
|
||||
}
|
||||
},
|
||||
|
||||
postMountWrapper: function(inst : Fiber, props : Object) {
|
||||
postMountWrapper: function(element : Element, props : Object) {
|
||||
var node = ((element : any) : TextAreaWithWrapperState);
|
||||
// This is in postMount because we need access to the DOM node, which is not
|
||||
// available until after the component has mounted.
|
||||
var node = ReactDOMComponentTree.getNodeFromInstance(inst);
|
||||
var textContent = node.textContent;
|
||||
|
||||
// Only set node.value if textContent is equal to the expected
|
||||
// initial value. In IE10/IE11 there is a bug where the placeholder attribute
|
||||
// will populate textContent as well.
|
||||
// https://developer.microsoft.com/microsoft-edge/platform/issues/101525/
|
||||
if (textContent === inst._wrapperState.initialValue) {
|
||||
if (textContent === node._wrapperState.initialValue) {
|
||||
node.value = textContent;
|
||||
}
|
||||
},
|
||||
|
||||
restoreControlledState: function(inst : Fiber, props : Object) {
|
||||
restoreControlledState: function(element : Element, props : Object) {
|
||||
// DOM component is still mounted; update
|
||||
ReactDOMTextarea.updateWrapper(inst, props);
|
||||
ReactDOMTextarea.updateWrapper(element, props);
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user