Refactor DOM special cases per tags including controlled fields (#26501)
I use a shared helper when setting properties into a helper whether it's initial or update. I moved the special cases per tag to commit phase so we can check it only once. This also effectively inlines getHostProps which can be done in a single check per prop key. The diffProperties operation is simplified to mostly just generating a plain diff of all properties, generating an update payload. This might generate a few more entries that are now ignored in the commit phase. that previously would've been ignored earlier. We could skip this and just do the whole diff in the commit phase by always scheduling a commit phase update. I tested the attribute table (one change documented below) and a few select DOM fixtures.
This commit is contained in:
parent
5cbe6258bc
commit
85de6fde51
File diff suppressed because it is too large
Load Diff
|
@ -15,19 +15,22 @@ import {getToStringValue, toString} from './ToStringValue';
|
|||
import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes';
|
||||
import {updateValueIfChanged} from './inputValueTracking';
|
||||
import getActiveElement from './getActiveElement';
|
||||
import assign from 'shared/assign';
|
||||
import {disableInputAttributeSyncing} from 'shared/ReactFeatureFlags';
|
||||
import {checkAttributeStringCoercion} from 'shared/CheckStringCoercion';
|
||||
|
||||
import type {ToStringValue} from './ToStringValue';
|
||||
|
||||
type InputWithWrapperState = HTMLInputElement & {
|
||||
export type InputWithWrapperState = HTMLInputElement & {
|
||||
_wrapperState: {
|
||||
initialValue: ToStringValue,
|
||||
initialChecked: ?boolean,
|
||||
controlled?: boolean,
|
||||
...
|
||||
},
|
||||
checked: boolean,
|
||||
value: string,
|
||||
defaultChecked: boolean,
|
||||
defaultValue: string,
|
||||
...
|
||||
};
|
||||
|
||||
|
@ -58,20 +61,6 @@ function isControlled(props: any) {
|
|||
* See http://www.w3.org/TR/2012/WD-html5-20121025/the-input-element.html
|
||||
*/
|
||||
|
||||
export function getHostProps(element: Element, props: Object): Object {
|
||||
const node = ((element: any): InputWithWrapperState);
|
||||
const checked = props.checked;
|
||||
|
||||
const hostProps = assign({}, props, {
|
||||
defaultChecked: undefined,
|
||||
defaultValue: undefined,
|
||||
value: undefined,
|
||||
checked: checked != null ? checked : node._wrapperState.initialChecked,
|
||||
});
|
||||
|
||||
return hostProps;
|
||||
}
|
||||
|
||||
export function initWrapperState(element: Element, props: Object) {
|
||||
if (__DEV__) {
|
||||
checkControlledValueProps('input', props);
|
||||
|
@ -114,10 +103,13 @@ export function initWrapperState(element: Element, props: Object) {
|
|||
|
||||
const node = ((element: any): InputWithWrapperState);
|
||||
const defaultValue = props.defaultValue == null ? '' : props.defaultValue;
|
||||
|
||||
const initialChecked =
|
||||
props.checked != null ? props.checked : props.defaultChecked;
|
||||
node._wrapperState = {
|
||||
initialChecked:
|
||||
props.checked != null ? props.checked : props.defaultChecked,
|
||||
typeof initialChecked !== 'function' &&
|
||||
typeof initialChecked !== 'symbol' &&
|
||||
!!initialChecked,
|
||||
initialValue: getToStringValue(
|
||||
props.value != null ? props.value : defaultValue,
|
||||
),
|
||||
|
@ -312,15 +304,16 @@ export function postMountWrapper(
|
|||
node.name = '';
|
||||
}
|
||||
|
||||
if (disableInputAttributeSyncing) {
|
||||
// When not syncing the checked attribute, the checked property
|
||||
// never gets assigned. It must be manually set. We don't want
|
||||
// to do this when hydrating so that existing user input isn't
|
||||
// modified
|
||||
if (!isHydrating) {
|
||||
updateChecked(element, props);
|
||||
}
|
||||
// The checked property never gets assigned. It must be manually set.
|
||||
// We don't want to do this when hydrating so that existing user input isn't
|
||||
// modified
|
||||
// TODO: I'm pretty sure this is a bug because initialValueTracking won't be
|
||||
// correct for the hydration case then.
|
||||
if (!isHydrating) {
|
||||
node.checked = !!node._wrapperState.initialChecked;
|
||||
}
|
||||
|
||||
if (disableInputAttributeSyncing) {
|
||||
// Only assign the checked attribute if it is defined. This saves
|
||||
// a DOM write when controlling the checked attribute isn't needed
|
||||
// (text inputs, submit/reset)
|
||||
|
|
|
@ -12,7 +12,6 @@ import {getCurrentFiberOwnerNameInDevOrNull} from 'react-reconciler/src/ReactCur
|
|||
|
||||
import {checkControlledValueProps} from '../shared/ReactControlledValuePropTypes';
|
||||
import {getToStringValue, toString} from './ToStringValue';
|
||||
import assign from 'shared/assign';
|
||||
import isArray from 'shared/isArray';
|
||||
|
||||
let didWarnValueDefaultValue;
|
||||
|
@ -130,12 +129,6 @@ function updateOptions(
|
|||
* selected.
|
||||
*/
|
||||
|
||||
export function getHostProps(element: Element, props: Object): Object {
|
||||
return assign({}, props, {
|
||||
value: undefined,
|
||||
});
|
||||
}
|
||||
|
||||
export function initWrapperState(element: Element, props: Object) {
|
||||
const node = ((element: any): SelectWithWrapperState);
|
||||
if (__DEV__) {
|
||||
|
|
|
@ -17,7 +17,7 @@ import {disableTextareaChildren} from 'shared/ReactFeatureFlags';
|
|||
|
||||
let didWarnValDefaultVal = false;
|
||||
|
||||
type TextAreaWithWrapperState = HTMLTextAreaElement & {
|
||||
export type TextAreaWithWrapperState = HTMLTextAreaElement & {
|
||||
_wrapperState: {initialValue: ToStringValue},
|
||||
};
|
||||
|
||||
|
@ -37,31 +37,6 @@ type TextAreaWithWrapperState = HTMLTextAreaElement & {
|
|||
* `defaultValue` if specified, or the children content (deprecated).
|
||||
*/
|
||||
|
||||
export function getHostProps(element: Element, props: Object): Object {
|
||||
const node = ((element: any): TextAreaWithWrapperState);
|
||||
|
||||
if (props.dangerouslySetInnerHTML != null) {
|
||||
throw new Error(
|
||||
'`dangerouslySetInnerHTML` does not make sense on <textarea>.',
|
||||
);
|
||||
}
|
||||
|
||||
// Always set children to the same thing. In IE9, the selection range will
|
||||
// get reset if `textContent` is mutated. We could add a check in setTextContent
|
||||
// to only set the value if/when the value differs from the node value (which would
|
||||
// completely solve this IE9 bug), but Sebastian+Sophie seemed to like this
|
||||
// solution. The value can be a boolean or object so that's why it's forced
|
||||
// to be a string.
|
||||
const hostProps = {
|
||||
...props,
|
||||
value: undefined,
|
||||
defaultValue: undefined,
|
||||
children: toString(node._wrapperState.initialValue),
|
||||
};
|
||||
|
||||
return hostProps;
|
||||
}
|
||||
|
||||
export function initWrapperState(element: Element, props: Object) {
|
||||
const node = ((element: any): TextAreaWithWrapperState);
|
||||
if (__DEV__) {
|
||||
|
@ -120,8 +95,10 @@ export function initWrapperState(element: Element, props: Object) {
|
|||
initialValue = defaultValue;
|
||||
}
|
||||
|
||||
const stringValue = getToStringValue(initialValue);
|
||||
node.defaultValue = (stringValue: any); // This will be toString:ed.
|
||||
node._wrapperState = {
|
||||
initialValue: getToStringValue(initialValue),
|
||||
initialValue: stringValue,
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -133,6 +133,42 @@ describe('ChangeEventPlugin', () => {
|
|||
}
|
||||
});
|
||||
|
||||
it('should not invoke a change event for textarea same value', () => {
|
||||
let called = 0;
|
||||
|
||||
function cb(e) {
|
||||
called++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
const node = ReactDOM.render(
|
||||
<textarea onChange={cb} defaultValue="initial" />,
|
||||
container,
|
||||
);
|
||||
node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
|
||||
node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
|
||||
// There should be no React change events because the value stayed the same.
|
||||
expect(called).toBe(0);
|
||||
});
|
||||
|
||||
it('should not invoke a change event for textarea same value (capture)', () => {
|
||||
let called = 0;
|
||||
|
||||
function cb(e) {
|
||||
called++;
|
||||
expect(e.type).toBe('change');
|
||||
}
|
||||
|
||||
const node = ReactDOM.render(
|
||||
<textarea onChangeCapture={cb} defaultValue="initial" />,
|
||||
container,
|
||||
);
|
||||
node.dispatchEvent(new Event('input', {bubbles: true, cancelable: true}));
|
||||
node.dispatchEvent(new Event('change', {bubbles: true, cancelable: true}));
|
||||
// There should be no React change events because the value stayed the same.
|
||||
expect(called).toBe(0);
|
||||
});
|
||||
|
||||
it('should consider initial checkbox checked=true to be current', () => {
|
||||
let called = 0;
|
||||
|
||||
|
|
Loading…
Reference in New Issue