/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * 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 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 * and write to updatePayload the values that changed or were deleted * * @param {?object} updatePayload Overriden with the props that changed. * @param {!object} prevProps Previous properties to diff against current * properties. These properties are as supplied to component construction. * @param {!object} prevProps Next "current" properties to diff against * previous. These properties are as supplied to component construction. * @return {?object} */ function diffRawProperties( updatePayload: ?Object, prevProps: ?Object, nextProps: ?Object, validAttributes: Object ): ?Object { var validAttributeConfig; var nextProp; var prevProp; var isScalar; var shouldUpdate; var differ; if (nextProps) { for (var propKey in nextProps) { validAttributeConfig = validAttributes[propKey]; if (!validAttributeConfig) { continue; // not a valid native prop } prevProp = prevProps && prevProps[propKey]; nextProp = nextProps[propKey]; // functions are converted to booleans as markers that the associated // events should be sent from native. if (typeof prevProp === 'function') { prevProp = true; } if (typeof nextProp === 'function') { nextProp = true; } if (prevProp !== nextProp) { // Scalars and new props are always updated. Objects use deepDiffer by // default, but can be optimized with custom differs. differ = validAttributeConfig.diff || deepDiffer; isScalar = typeof nextProp !== 'object' || nextProp === null; shouldUpdate = isScalar || !prevProp || differ(prevProp, nextProp); if (shouldUpdate) { updatePayload = updatePayload || {}; updatePayload[propKey] = nextProp; } } } } // Also iterate through all the previous props to catch any that have been // removed and make sure native gets the signal so it can reset them to the // default. if (prevProps) { for (var propKey in prevProps) { validAttributeConfig = validAttributes[propKey]; if (!validAttributeConfig) { continue; // not a valid native prop } if (updatePayload && updatePayload[propKey] !== undefined) { continue; // Prop already specified } prevProp = prevProps[propKey]; nextProp = nextProps && nextProps[propKey]; // functions are converted to booleans as markers that the associated // events should be sent from native. if (typeof prevProp === 'function') { prevProp = true; } if (typeof nextProp === 'function') { nextProp = true; } if (prevProp !== nextProp) { if (nextProp === undefined) { nextProp = null; // null is a sentinel we explicitly send to native } // Scalars and new props are always updated. Objects use deepDiffer by // default, but can be optimized with custom differs. differ = validAttributeConfig.diff || deepDiffer; isScalar = typeof nextProp !== 'object' || nextProp === null; shouldUpdate = isScalar && prevProp !== nextProp || differ(prevProp, nextProp); if (shouldUpdate) { updatePayload = updatePayload || {}; updatePayload[propKey] = nextProp; } } } } return updatePayload; } 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;