Skip to content

Commit

Permalink
Refactor Attribute Processing (Step 1)
Browse files Browse the repository at this point in the history
Summary: Concolidate the creation of the "update payload" into
ReactNativeAttributePayload which now has a create
and a diff version. The create version can be used by
both mountComponent and setNativeProps. This means that
diffRawProperties moves into ReactNativeAttributePayload.

Instead of storing previousFlattenedStyle as memoized
state in the component tree, I recalculate it every
time. This allows better use of the generational GC.
However, it is still probably a fairly expensive
technique so I will follow it up with a diff that
walks both nested array trees to do the diffing in a
follow up.

This is the first diff of several steps.

@​public

Reviewed By: @vjeux

Differential Revision: D2440644

fb-gh-sync-id: 1d0da4f6e2bf716f33e119df947c044abb918471
  • Loading branch information
sebmarkbage authored and facebook-github-bot-4 committed Oct 6, 2015
1 parent 62e8ddc commit 6c5024e
Show file tree
Hide file tree
Showing 6 changed files with 118 additions and 172 deletions.
44 changes: 4 additions & 40 deletions Libraries/ReactIOS/NativeMethodsMixin.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

var NativeModules = require('NativeModules');
var RCTUIManager = NativeModules.UIManager;
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var TextInputState = require('TextInputState');

var findNodeHandle = require('findNodeHandle');
Expand Down Expand Up @@ -65,52 +66,15 @@ var NativeMethodsMixin = {
* next render, they will remain active.
*/
setNativeProps: function(nativeProps: Object) {
// nativeProps contains a style attribute that's going to be flattened
// and all the attributes expanded in place. In order to make this
// process do as few allocations and copies as possible, we return
// one if the other is empty. Only if both have values then we create
// a new object and merge.
var hasOnlyStyle = true;
for (var key in nativeProps) {
if (key !== 'style') {
hasOnlyStyle = false;
break;
}
}

var validAttributes = this.viewConfig.validAttributes;
var hasProcessedProps = false;
var processedProps = {};
for (var key in nativeProps) {
var process = validAttributes[key] && validAttributes[key].process;
if (process) {
hasProcessedProps = true;
processedProps[key] = process(nativeProps[key]);
}
}

var style = precomputeStyle(
flattenStyle(processedProps.style || nativeProps.style),
var updatePayload = ReactNativeAttributePayload.create(
nativeProps,
this.viewConfig.validAttributes
);

var props = null;
if (hasOnlyStyle) {
props = style;
} else {
props = nativeProps;
if (hasProcessedProps) {
props = mergeFast(props, processedProps);
}
if (style) {
props = mergeFast(props, style);
}
}

RCTUIManager.updateView(
findNodeHandle(this),
this.viewConfig.uiViewClassName,
props
updatePayload
);
},

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,18 @@
* LICENSE file in the root directory of this source tree. An additional grant
* of patent rights can be found in the PATENTS file in the same directory.
*
* @providesModule diffRawProperties
* @providesModule ReactNativeAttributePayload
* @flow
*/
'use strict';

var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');

var deepDiffer = require('deepDiffer');
var styleDiffer = require('styleDiffer');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
var flattenStyle = require('flattenStyle');
var precomputeStyle = require('precomputeStyle');

/**
* diffRawProperties takes two sets of props and a set of valid attributes
Expand Down Expand Up @@ -115,4 +121,72 @@ function diffRawProperties(
return updatePayload;
}

module.exports = diffRawProperties;
var ReactNativeAttributePayload = {

create: function(
props : Object,
validAttributes : Object
) : ?Object {
return ReactNativeAttributePayload.diff({}, props, validAttributes);
},

diff: function(
prevProps : Object,
nextProps : Object,
validAttributes : Object
) : ?Object {

if (__DEV__) {
for (var key in nextProps) {
if (nextProps.hasOwnProperty(key) &&
nextProps[key] &&
validAttributes[key]) {
deepFreezeAndThrowOnMutationInDev(nextProps[key]);
}
}
}

var updatePayload = diffRawProperties(
null, // updatePayload
prevProps,
nextProps,
validAttributes
);

for (var key in updatePayload) {
var process = validAttributes[key] && validAttributes[key].process;
if (process) {
updatePayload[key] = process(updatePayload[key]);
}
}

// The style property is a deeply nested element which includes numbers
// to represent static objects. Most of the time, it doesn't change across
// renders, so it's faster to spend the time checking if it is different
// before actually doing the expensive flattening operation in order to
// compute the diff.
if (styleDiffer(nextProps.style, prevProps.style)) {
// TODO: Use a cached copy of previousFlattenedStyle, or walk both
// props in parallel.
var previousFlattenedStyle = precomputeStyle(
flattenStyle(prevProps.style),
validAttributes
);
var nextFlattenedStyle = precomputeStyle(
flattenStyle(nextProps.style),
validAttributes
);
updatePayload = diffRawProperties(
updatePayload,
previousFlattenedStyle,
nextFlattenedStyle,
ReactNativeStyleAttributes
);
}

return updatePayload;
}

};

module.exports = ReactNativeAttributePayload;
71 changes: 4 additions & 67 deletions Libraries/ReactNative/ReactNativeBaseComponent.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,13 @@
'use strict';

var NativeMethodsMixin = require('NativeMethodsMixin');
var ReactNativeAttributePayload = require('ReactNativeAttributePayload');
var ReactNativeEventEmitter = require('ReactNativeEventEmitter');
var ReactNativeStyleAttributes = require('ReactNativeStyleAttributes');
var ReactNativeTagHandles = require('ReactNativeTagHandles');
var ReactMultiChild = require('ReactMultiChild');
var RCTUIManager = require('NativeModules').UIManager;

var styleDiffer = require('styleDiffer');
var deepFreezeAndThrowOnMutationInDev = require('deepFreezeAndThrowOnMutationInDev');
var diffRawProperties = require('diffRawProperties');
var flattenStyle = require('flattenStyle');
var precomputeStyle = require('precomputeStyle');
var warning = require('warning');

var registrationNames = ReactNativeEventEmitter.registrationNames;
Expand Down Expand Up @@ -131,63 +127,6 @@ ReactNativeBaseComponent.Mixin = {
}
},


/**
* Beware, this function has side effect to store this.previousFlattenedStyle!
*
* @param {!object} prevProps Previous properties
* @param {!object} nextProps Next properties
* @param {!object} validAttributes Set of valid attributes and how they
* should be diffed
*/
computeUpdatedProperties: function(prevProps, nextProps, validAttributes) {
if (__DEV__) {
for (var key in nextProps) {
if (nextProps.hasOwnProperty(key) &&
nextProps[key] &&
validAttributes[key]) {
deepFreezeAndThrowOnMutationInDev(nextProps[key]);
}
}
}

var updatePayload = diffRawProperties(
null, // updatePayload
prevProps,
nextProps,
validAttributes
);

for (var key in updatePayload) {
var process = validAttributes[key] && validAttributes[key].process;
if (process) {
updatePayload[key] = process(updatePayload[key]);
}
}

// The style property is a deeply nested element which includes numbers
// to represent static objects. Most of the time, it doesn't change across
// renders, so it's faster to spend the time checking if it is different
// before actually doing the expensive flattening operation in order to
// compute the diff.
if (styleDiffer(nextProps.style, prevProps.style)) {
var nextFlattenedStyle = precomputeStyle(
flattenStyle(nextProps.style),
this.viewConfig.validAttributes
);
updatePayload = diffRawProperties(
updatePayload,
this.previousFlattenedStyle,
nextFlattenedStyle,
ReactNativeStyleAttributes
);
this.previousFlattenedStyle = nextFlattenedStyle;
}

return updatePayload;
},


/**
* Updates the component's currently mounted representation.
*
Expand All @@ -200,7 +139,7 @@ ReactNativeBaseComponent.Mixin = {
var prevElement = this._currentElement;
this._currentElement = nextElement;

var updatePayload = this.computeUpdatedProperties(
var updatePayload = ReactNativeAttributePayload.diff(
prevElement.props,
nextElement.props,
this.viewConfig.validAttributes
Expand Down Expand Up @@ -262,10 +201,8 @@ ReactNativeBaseComponent.Mixin = {

var tag = ReactNativeTagHandles.allocateTag();

this.previousFlattenedStyle = {};
var updatePayload = this.computeUpdatedProperties(
{}, // previous props
this._currentElement.props, // next props
var updatePayload = ReactNativeAttributePayload.create(
this._currentElement.props,
this.viewConfig.validAttributes
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -242,13 +242,7 @@ var variants = {

var validAttributes = require('ReactNativeViewAttributes').UIView;

var ReactNativeBaseComponent = require('ReactNativeBaseComponent');
ReactNativeBaseComponent.prototype.diff =
ReactNativeBaseComponent.prototype.computeUpdatedProperties;
var Differ = new ReactNativeBaseComponent({
validAttributes: validAttributes,
uiViewClassName: 'Differ'
});
var Differ = require('ReactNativeAttributePayload');

// Runner

Expand Down
Loading

0 comments on commit 6c5024e

Please sign in to comment.