301
return tagName.indexOf('-') >= 0 || props.is != null;
302
}
303
304
-/**
305
- * Reconciles the properties by detecting differences in property values and
306
- * updating the DOM as necessary. This function is probably the single most
307
- * critical path for performance optimization.
308
- *
309
- * TODO: Benchmark whether checking for changed values in memory actually
310
- * improves performance (especially statically positioned elements).
311
- * TODO: Benchmark the effects of putting this at the top since 99% of props
312
- * do not change for a given reconciliation.
313
- * TODO: Benchmark areas that can be improved with caching.
314
- */
315
-function updateDOMProperties(
304
+function setInitialDOMProperties(
305
domElement : Element,
306
rootContainerElement : Element,
318
- lastProps : null | Object,
307
nextProps : Object,
320
- wasCustomComponentTag : boolean,
308
isCustomComponentTag : boolean,
309
) : void {
323
- var propKey;
324
- var styleName;
325
- var styleUpdates;
326
- for (propKey in lastProps) {
327
- if (nextProps.hasOwnProperty(propKey) ||
328
- !lastProps.hasOwnProperty(propKey) ||
329
- lastProps[propKey] == null) {
330
- continue;
331
- }
332
- if (propKey === STYLE) {
333
- var lastStyle = lastProps[propKey];
334
- for (styleName in lastStyle) {
335
- if (lastStyle.hasOwnProperty(styleName)) {
336
- styleUpdates = styleUpdates || {};
337
- styleUpdates[styleName] = '';
338
- }
339
- }
340
- } else if (propKey === DANGEROUSLY_SET_INNER_HTML ||
341
- propKey === CHILDREN) {
342
- // TODO: Clear innerHTML. This is currently broken in Fiber because we are
343
- // too late to clear everything at this point because new children have
344
- // already been inserted.
345
- } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
346
- // Noop
347
- } else if (registrationNameModules.hasOwnProperty(propKey)) {
348
- // Do nothing for deleted listeners.
349
- } else if (wasCustomComponentTag) {
350
- DOMPropertyOperations.deleteValueForAttribute(
351
- domElement,
352
- propKey
353
- );
354
- } else if (
355
- DOMProperty.properties[propKey] ||
356
- DOMProperty.isCustomAttribute(propKey)) {
357
- DOMPropertyOperations.deleteValueForProperty(domElement, propKey);
358
- }
359
- }
360
- for (propKey in nextProps) {
310
+ for (var propKey in nextProps) {
311
var nextProp = nextProps[propKey];
362
- var lastProp =
363
- lastProps != null ? lastProps[propKey] : undefined;
364
- if (!nextProps.hasOwnProperty(propKey) ||
365
- nextProp === lastProp ||
366
- nextProp == null && lastProp == null) {
312
+ if (!nextProps.hasOwnProperty(propKey)) {
313
continue;
314
}
315
if (propKey === STYLE) {
320
Object.freeze(nextProp);
321
}
322
}
377
- if (lastProp) {
378
- // Unset styles on `lastProp` but not on `nextProp`.
379
- for (styleName in lastProp) {
380
- if (lastProp.hasOwnProperty(styleName) &&
381
- (!nextProp || !nextProp.hasOwnProperty(styleName))) {
382
- styleUpdates = styleUpdates || {};
383
- styleUpdates[styleName] = '';
384
- }
385
- }
386
- // Update styles that changed since `lastProp`.
387
- for (styleName in nextProp) {
388
- if (nextProp.hasOwnProperty(styleName) &&
389
- lastProp[styleName] !== nextProp[styleName]) {
390
- styleUpdates = styleUpdates || {};
391
- styleUpdates[styleName] = nextProp[styleName];
392
- }
393
- }
394
- } else {
395
- // Relies on `updateStylesByID` not mutating `styleUpdates`.
396
- styleUpdates = nextProp;
397
- }
323
+ // Relies on `updateStylesByID` not mutating `styleUpdates`.
324
+ // TODO: call ReactInstrumentation.debugTool.onHostOperation in DEV.
325
+ CSSPropertyOperations.setValueForStyles(
326
+ domElement,
327
+ nextProp,
328
+ );
329
} else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
330
var nextHtml = nextProp ? nextProp[HTML] : undefined;
400
- var lastHtml = lastProp ? lastProp[HTML] : undefined;
401
- // Intentional use of != to avoid catching zero/false.
331
if (nextHtml != null) {
403
- if (lastHtml !== nextHtml) {
404
- setInnerHTML(domElement, '' + nextHtml);
405
- }
406
- } else {
407
- // TODO: It might be too late to clear this if we have children
408
- // inserted already.
332
+ setInnerHTML(domElement, nextHtml);
333
}
334
} else if (propKey === CHILDREN) {
335
if (typeof nextProp === 'string') {
357
// brings us in line with the same behavior we have on initial render.
358
if (nextProp != null) {
359
DOMPropertyOperations.setValueForProperty(domElement, propKey, nextProp);
360
+ }
361
+ }
362
+ }
363
+}
364
+
365
+function updateDOMProperties(
366
+ domElement : Element,
367
+ updatePayload : Array<any>,
368
+ wasCustomComponentTag : boolean,
369
+ isCustomComponentTag : boolean,
370
+) : void {
371
+ // TODO: Handle wasCustomComponentTag
372
+ for (var i = 0; i < updatePayload.length; i+=2) {
373
+ var propKey = updatePayload[i];
374
+ var propValue = updatePayload[i + 1];
375
+ if (propKey === STYLE) {
376
+ // TODO: call ReactInstrumentation.debugTool.onHostOperation in DEV.
377
+ CSSPropertyOperations.setValueForStyles(
378
+ domElement,
379
+ propValue,
380
+ );
381
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
382
+ setInnerHTML(domElement, propValue);
383
+ } else if (propKey === CHILDREN) {
384
+ setTextContent(domElement, propValue);
385
+ } else if (isCustomComponentTag) {
386
+ if (propValue != null) {
387
+ DOMPropertyOperations.setValueForAttribute(
388
+ domElement,
389
+ propKey,
390
+ propValue
391
+ );
392
+ } else {
393
+ DOMPropertyOperations.deleteValueForAttribute(
394
+ domElement,
395
+ propKey
396
+ );
397
+ }
398
+ } else if (
399
+ DOMProperty.properties[propKey] ||
400
+ DOMProperty.isCustomAttribute(propKey)) {
401
+ // If we're updating to null or undefined, we should remove the property
402
+ // from the DOM node instead of inadvertently setting to a string. This
403
+ // brings us in line with the same behavior we have on initial render.
404
+ if (propValue != null) {
405
+ DOMPropertyOperations.setValueForProperty(domElement, propKey, propValue);
406
} else {
407
DOMPropertyOperations.deleteValueForProperty(domElement, propKey);
408
}
409
}
410
}
441
- if (styleUpdates) {
442
- // TODO: call ReactInstrumentation.debugTool.onHostOperation in DEV.
443
- CSSPropertyOperations.setValueForStyles(
444
- domElement,
445
- styleUpdates,
446
- );
447
- }
411
}
412
413
// Assumes there is no parent namespace.
555
556
assertValidProps(tag, props);
557
595
- updateDOMProperties(
558
+ setInitialDOMProperties(
559
domElement,
560
rootContainerElement,
598
- null,
561
props,
600
- false,
562
isCustomComponentTag
563
);
564
587
}
588
},
589
629
- updateProperties(
590
+ // Calculate the diff between the two objects.
591
+ diffProperties(
592
domElement : Element,
593
tag : string,
594
lastRawProps : Object,
595
nextRawProps : Object,
596
rootContainerElement : Element,
635
- ) : void {
597
+ ) : null | Array<mixed> {
598
if (__DEV__) {
599
validatePropertiesInDevelopment(tag, nextRawProps);
600
}
601
602
+ var updatePayload : null | Array<any> = null;
603
+
604
var lastProps : Object;
605
var nextProps : Object;
606
switch (tag) {
607
case 'input':
608
lastProps = ReactDOMFiberInput.getHostProps(domElement, lastRawProps);
609
nextProps = ReactDOMFiberInput.getHostProps(domElement, nextRawProps);
610
+ updatePayload = [];
611
break;
612
case 'option':
613
lastProps = ReactDOMFiberOption.getHostProps(domElement, lastRawProps);
614
nextProps = ReactDOMFiberOption.getHostProps(domElement, nextRawProps);
615
+ updatePayload = [];
616
break;
617
case 'select':
618
lastProps = ReactDOMFiberSelect.getHostProps(domElement, lastRawProps);
619
nextProps = ReactDOMFiberSelect.getHostProps(domElement, nextRawProps);
620
+ updatePayload = [];
621
break;
622
case 'textarea':
623
lastProps = ReactDOMFiberTextarea.getHostProps(domElement, lastRawProps);
624
nextProps = ReactDOMFiberTextarea.getHostProps(domElement, nextRawProps);
625
+ updatePayload = [];
626
break;
627
default:
628
lastProps = lastRawProps;
636
}
637
638
assertValidProps(tag, nextProps);
671
- var wasCustomComponentTag = isCustomComponent(tag, lastProps);
672
- var isCustomComponentTag = isCustomComponent(tag, nextProps);
639
+
640
+ var propKey;
641
+ var styleName;
642
+ var styleUpdates = null;
643
+ for (propKey in lastProps) {
644
+ if (nextProps.hasOwnProperty(propKey) ||
645
+ !lastProps.hasOwnProperty(propKey) ||
646
+ lastProps[propKey] == null) {
647
+ continue;
648
+ }
649
+ if (propKey === STYLE) {
650
+ var lastStyle = lastProps[propKey];
651
+ for (styleName in lastStyle) {
652
+ if (lastStyle.hasOwnProperty(styleName)) {
653
+ if (!styleUpdates) {
654
+ styleUpdates = {};
655
+ }
656
+ styleUpdates[styleName] = '';
657
+ }
658
+ }
659
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML ||
660
+ propKey === CHILDREN) {
661
+ // Noop. This is handled by the clear text mechanism.
662
+ } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
663
+ // Noop
664
+ } else if (registrationNameModules.hasOwnProperty(propKey)) {
665
+ // This is a special case. If any listener updates we need to ensure
666
+ // that the "current" fiber pointer gets updated so we need a commit
667
+ // to update this element.
668
+ if (!updatePayload) {
669
+ updatePayload = [];
670
+ }
671
+ } else {
672
+ // For all other deleted properties we add it to the queue. We use
673
+ // the whitelist in the commit phase instead.
674
+ (updatePayload = updatePayload || []).push(propKey, null);
675
+ }
676
+ }
677
+ for (propKey in nextProps) {
678
+ var nextProp = nextProps[propKey];
679
+ var lastProp =
680
+ lastProps != null ? lastProps[propKey] : undefined;
681
+ if (!nextProps.hasOwnProperty(propKey) ||
682
+ nextProp === lastProp ||
683
+ nextProp == null && lastProp == null) {
684
+ continue;
685
+ }
686
+ if (propKey === STYLE) {
687
+ if (__DEV__) {
688
+ if (nextProp) {
689
+ // Freeze the next style object so that we can assume it won't be
690
+ // mutated. We have already warned for this in the past.
691
+ Object.freeze(nextProp);
692
+ }
693
+ }
694
+ if (lastProp) {
695
+ // Unset styles on `lastProp` but not on `nextProp`.
696
+ for (styleName in lastProp) {
697
+ if (lastProp.hasOwnProperty(styleName) &&
698
+ (!nextProp || !nextProp.hasOwnProperty(styleName))) {
699
+ if (!styleUpdates) {
700
+ styleUpdates = {};
701
+ }
702
+ styleUpdates[styleName] = '';
703
+ }
704
+ }
705
+ // Update styles that changed since `lastProp`.
706
+ for (styleName in nextProp) {
707
+ if (nextProp.hasOwnProperty(styleName) &&
708
+ lastProp[styleName] !== nextProp[styleName]) {
709
+ if (!styleUpdates) {
710
+ styleUpdates = {};
711
+ }
712
+ styleUpdates[styleName] = nextProp[styleName];
713
+ }
714
+ }
715
+ } else {
716
+ // Relies on `updateStylesByID` not mutating `styleUpdates`.
717
+ if (!styleUpdates) {
718
+ if (!updatePayload) {
719
+ updatePayload = [];
720
+ }
721
+ updatePayload.push(propKey, styleUpdates);
722
+ }
723
+ styleUpdates = nextProp;
724
+ }
725
+ } else if (propKey === DANGEROUSLY_SET_INNER_HTML) {
726
+ var nextHtml = nextProp ? nextProp[HTML] : undefined;
727
+ var lastHtml = lastProp ? lastProp[HTML] : undefined;
728
+ if (nextHtml != null) {
729
+ if (lastHtml !== nextHtml) {
730
+ (updatePayload = updatePayload || []).push(propKey, '' + nextHtml);
731
+ }
732
+ } else {
733
+ // TODO: It might be too late to clear this if we have children
734
+ // inserted already.
735
+ }
736
+ } else if (propKey === CHILDREN) {
737
+ if (lastProp !== nextProp && (
738
+ typeof nextProp === 'string' || typeof nextProp === 'number'
739
+ )) {
740
+ (updatePayload = updatePayload || []).push(propKey, '' + nextProp);
741
+ }
742
+ } else if (propKey === SUPPRESS_CONTENT_EDITABLE_WARNING) {
743
+ // Noop
744
+ } else if (registrationNameModules.hasOwnProperty(propKey)) {
745
+ if (nextProp) {
746
+ // We eagerly listen to this even though we haven't committed yet.
747
+ ensureListeningTo(rootContainerElement, propKey);
748
+ }
749
+ if (!updatePayload && lastProp !== nextProp) {
750
+ // This is a special case. If any listener updates we need to ensure
751
+ // that the "current" props pointer gets updated so we need a commit
752
+ // to update this element.
753
+ updatePayload = [];
754
+ }
755
+ } else {
756
+ // For any other property we always add it to the queue and then we
757
+ // filter it out using the whitelist during the commit.
758
+ (updatePayload = updatePayload || []).push(propKey, nextProp);
759
+ }
760
+ }
761
+ if (styleUpdates) {
762
+ (updatePayload = updatePayload || []).push(STYLE, styleUpdates);
763
+ }
764
+ return updatePayload;
765
+ },
766
+
767
+ // Apply the diff.
768
+ updateProperties(
769
+ domElement : Element,
770
+ updatePayload : Array<any>,
771
+ tag : string,
772
+ lastRawProps : Object,
773
+ nextRawProps : Object
774
+ ) : void {
775
+ var wasCustomComponentTag = isCustomComponent(tag, lastRawProps);
776
+ var isCustomComponentTag = isCustomComponent(tag, nextRawProps);
777
+ // Apply the diff.
778
updateDOMProperties(
779
domElement,
675
- rootContainerElement,
676
- lastProps,
677
- nextProps,
780
+ updatePayload,
781
wasCustomComponentTag,
782
isCustomComponentTag
783
);
784
785
+ // TODO: Ensure that an update gets scheduled if any of the special props
786
+ // changed.
787
switch (tag) {
788
case 'input':
789
// Update the wrapper around inputs *after* updating props. This has to