mirror of
https://github.com/zhigang1992/react-native.git
synced 2026-02-13 17:26:34 +08:00
Summary: Move the ViewAttributes and StyleAttributes configuration into the Components library since they're coupled and change with the native component configuration. This also decouples StyleAttributes from the reconciler by adding it to the ReactViewAttributes. To do that, I refactored the property diffing to allow for recursive configurations. Now an attribute configuration can be a nested object, a custom configuration (diff/process) or true. The requireNativeComponent path incorrectly gets its attributes set up on the root validAttributes instead of the nested style object. So I also have to add the nested form. Effectively these currently allow these attributes on props or nested. @public Reviewed By: @vjeux Differential Revision: D2456842 fb-gh-sync-id: cd5405bd8316c2fcb016d06c61244ce7719c26c0
290 lines
8.4 KiB
JavaScript
290 lines
8.4 KiB
JavaScript
/**
|
|
* 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 deepDiffer = require('deepDiffer');
|
|
var styleDiffer = require('styleDiffer');
|
|
var flattenStyle = require('flattenStyle');
|
|
var precomputeStyle = require('precomputeStyle');
|
|
|
|
type AttributeDiffer = (prevProp : mixed, nextProp : mixed) => boolean;
|
|
type AttributePreprocessor = (nextProp: mixed) => mixed;
|
|
|
|
type CustomAttributeConfiguration =
|
|
{ diff : AttributeDiffer, process : AttributePreprocessor } |
|
|
{ diff : AttributeDiffer } |
|
|
{ process : AttributePreprocessor };
|
|
|
|
type AttributeConfiguration =
|
|
{ [key : string]: (
|
|
CustomAttributeConfiguration | AttributeConfiguration /*| boolean*/
|
|
) };
|
|
|
|
function defaultDiffer(prevProp: mixed, nextProp: mixed) : boolean {
|
|
if (typeof nextProp !== 'object' || nextProp === null) {
|
|
// Scalars have already been checked for equality
|
|
return true;
|
|
} else {
|
|
// For objects and arrays, the default diffing algorithm is a deep compare
|
|
return deepDiffer(prevProp, nextProp);
|
|
}
|
|
}
|
|
|
|
function diffNestedProperty(
|
|
updatePayload :? Object,
|
|
prevProp, // inferred
|
|
nextProp, // inferred
|
|
validAttributes : AttributeConfiguration
|
|
) : ?Object {
|
|
// 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(prevProp, nextProp)) {
|
|
return updatePayload;
|
|
}
|
|
|
|
// TODO: Walk both props in parallel instead of flattening.
|
|
// TODO: Move precomputeStyle to .process for each attribute.
|
|
|
|
var previousFlattenedStyle = precomputeStyle(
|
|
flattenStyle(prevProp),
|
|
validAttributes
|
|
);
|
|
|
|
var nextFlattenedStyle = precomputeStyle(
|
|
flattenStyle(nextProp),
|
|
validAttributes
|
|
);
|
|
|
|
if (!previousFlattenedStyle || !nextFlattenedStyle) {
|
|
if (nextFlattenedStyle) {
|
|
return addProperties(
|
|
updatePayload,
|
|
nextFlattenedStyle,
|
|
validAttributes
|
|
);
|
|
}
|
|
if (previousFlattenedStyle) {
|
|
return clearProperties(
|
|
updatePayload,
|
|
previousFlattenedStyle,
|
|
validAttributes
|
|
);
|
|
}
|
|
return updatePayload;
|
|
}
|
|
|
|
// recurse
|
|
return diffProperties(
|
|
updatePayload,
|
|
previousFlattenedStyle,
|
|
nextFlattenedStyle,
|
|
validAttributes
|
|
);
|
|
}
|
|
|
|
/**
|
|
* addNestedProperty takes a single set of props and valid attribute
|
|
* attribute configurations. It processes each prop and adds it to the
|
|
* updatePayload.
|
|
*/
|
|
/*
|
|
function addNestedProperty(
|
|
updatePayload :? Object,
|
|
nextProp : Object,
|
|
validAttributes : AttributeConfiguration
|
|
) {
|
|
// TODO: Fast path
|
|
return diffNestedProperty(updatePayload, {}, nextProp, validAttributes);
|
|
}
|
|
*/
|
|
|
|
/**
|
|
* clearNestedProperty takes a single set of props and valid attributes. It
|
|
* adds a null sentinel to the updatePayload, for each prop key.
|
|
*/
|
|
function clearNestedProperty(
|
|
updatePayload :? Object,
|
|
prevProp : Object,
|
|
validAttributes : AttributeConfiguration
|
|
) : ?Object {
|
|
// TODO: Fast path
|
|
return diffNestedProperty(updatePayload, prevProp, {}, validAttributes);
|
|
}
|
|
|
|
/**
|
|
* diffProperties takes two sets of props and a set of valid attributes
|
|
* and write to updatePayload the values that changed or were deleted.
|
|
* If no updatePayload is provided, a new one is created and returned if
|
|
* anything changed.
|
|
*/
|
|
function diffProperties(
|
|
updatePayload : ?Object,
|
|
prevProps : Object,
|
|
nextProps : Object,
|
|
validAttributes : AttributeConfiguration
|
|
): ?Object {
|
|
var attributeConfig : ?AttributeConfiguration;
|
|
var nextProp;
|
|
var prevProp;
|
|
|
|
for (var propKey in nextProps) {
|
|
attributeConfig = validAttributes[propKey];
|
|
if (!attributeConfig) {
|
|
continue; // not a valid native prop
|
|
}
|
|
if (updatePayload && updatePayload[propKey] !== undefined) {
|
|
// If we're in a nested attribute set, we may have set this property
|
|
// already. If so, bail out. The previous update is what counts.
|
|
continue;
|
|
}
|
|
prevProp = prevProps[propKey];
|
|
nextProp = nextProps[propKey];
|
|
|
|
// functions are converted to booleans as markers that the associated
|
|
// events should be sent from native.
|
|
if (typeof nextProp === 'function') {
|
|
nextProp = true;
|
|
// If nextProp is not a function, then don't bother changing prevProp
|
|
// since nextProp will win and go into the updatePayload regardless.
|
|
if (typeof prevProp === 'function') {
|
|
prevProp = true;
|
|
}
|
|
}
|
|
|
|
if (prevProp === nextProp) {
|
|
continue; // nothing changed
|
|
}
|
|
|
|
// Pattern match on: attributeConfig
|
|
if (typeof attributeConfig !== 'object') {
|
|
// case: !Object is the default case
|
|
if (defaultDiffer(prevProp, nextProp)) {
|
|
// a normal leaf has changed
|
|
(updatePayload || (updatePayload = {}))[propKey] = nextProp;
|
|
}
|
|
} else if (typeof attributeConfig.diff === 'function' ||
|
|
typeof attributeConfig.process === 'function') {
|
|
// case: CustomAttributeConfiguration
|
|
var shouldUpdate = prevProp === undefined || (
|
|
typeof attributeConfig.diff === 'function' ?
|
|
attributeConfig.diff(prevProp, nextProp) :
|
|
defaultDiffer(prevProp, nextProp)
|
|
);
|
|
if (shouldUpdate) {
|
|
var nextValue = typeof attributeConfig.process === 'function' ?
|
|
attributeConfig.process(nextProp) :
|
|
nextProp;
|
|
(updatePayload || (updatePayload = {}))[propKey] = nextValue;
|
|
}
|
|
} else {
|
|
// default: fallthrough case when nested properties are defined
|
|
updatePayload = diffNestedProperty(
|
|
updatePayload,
|
|
prevProp,
|
|
nextProp,
|
|
attributeConfig
|
|
);
|
|
}
|
|
}
|
|
|
|
// 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.
|
|
for (var propKey in prevProps) {
|
|
if (nextProps[propKey] !== undefined) {
|
|
continue; // we've already covered this key in the previous pass
|
|
}
|
|
attributeConfig = validAttributes[propKey];
|
|
if (!attributeConfig) {
|
|
continue; // not a valid native prop
|
|
}
|
|
prevProp = prevProps[propKey];
|
|
if (prevProp === undefined) {
|
|
continue; // was already empty anyway
|
|
}
|
|
// Pattern match on: attributeConfig
|
|
if (typeof attributeConfig !== 'object' ||
|
|
typeof attributeConfig.diff === 'function' ||
|
|
typeof attributeConfig.process === 'function') {
|
|
// case: CustomAttributeConfiguration | !Object
|
|
// Flag the leaf property for removal by sending a sentinel.
|
|
(updatePayload || (updatePayload = {}))[propKey] = null;
|
|
} else {
|
|
// default:
|
|
// This is a nested attribute configuration where all the properties
|
|
// were removed so we need to go through and clear out all of them.
|
|
updatePayload = clearNestedProperty(
|
|
updatePayload,
|
|
prevProp,
|
|
attributeConfig
|
|
);
|
|
}
|
|
}
|
|
return updatePayload;
|
|
}
|
|
|
|
/**
|
|
* addProperties adds all the valid props to the payload after being processed.
|
|
*/
|
|
function addProperties(
|
|
updatePayload : ?Object,
|
|
props : Object,
|
|
validAttributes : AttributeConfiguration
|
|
) : ?Object {
|
|
return diffProperties(updatePayload, {}, props, validAttributes);
|
|
}
|
|
|
|
/**
|
|
* clearProperties clears all the previous props by adding a null sentinel
|
|
* to the payload for each valid key.
|
|
*/
|
|
function clearProperties(
|
|
updatePayload : ?Object,
|
|
prevProps : Object,
|
|
validAttributes : AttributeConfiguration
|
|
) :? Object {
|
|
return diffProperties(updatePayload, prevProps, {}, validAttributes);
|
|
}
|
|
|
|
var ReactNativeAttributePayload = {
|
|
|
|
create: function(
|
|
props : Object,
|
|
validAttributes : AttributeConfiguration
|
|
) : ?Object {
|
|
return addProperties(
|
|
null, // updatePayload
|
|
props,
|
|
validAttributes
|
|
);
|
|
},
|
|
|
|
diff: function(
|
|
prevProps : Object,
|
|
nextProps : Object,
|
|
validAttributes : AttributeConfiguration
|
|
) : ?Object {
|
|
return diffProperties(
|
|
null, // updatePayload
|
|
prevProps,
|
|
nextProps,
|
|
validAttributes
|
|
);
|
|
}
|
|
|
|
};
|
|
|
|
module.exports = ReactNativeAttributePayload;
|