Skip to content

Commit

Permalink
Distinguish between string/number-valued interpolation nodes at the t…
Browse files Browse the repository at this point in the history
…ype level

Summary:
Changelog:
[General][Fixed] - Improved Flow type inference in Animated `.interpolate()`

Improves the ergonomics of `.interpolate()` by allowing Flow to infer the correct type for `outputRange`. This is achieved by adding a new type parameter `OutputT` to `interpolate()` (and `Animated.Interpolation` and `InterpolationConfigType`), which Flow infers as either `number` or `string` based on usage.

Admittedly, at the call site, this is not that much safer compared to something like `outputRange: $ReadOnlyArray<number | string>`, but it does document the intent of the API a bit better and provide some downstream type safety. For example, we can now express `Animated.Number` (D35869375) more precisely by excluding string-valued interpolation nodes.

Reviewed By: javache

Differential Revision: D35869725

fbshipit-source-id: e03ec22e9b3368ee196b392af011062ac99d8bb9
  • Loading branch information
motiz88 authored and facebook-github-bot committed May 3, 2022
1 parent 9eb7629 commit 7b86fa2
Show file tree
Hide file tree
Showing 13 changed files with 56 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Libraries/Animated/AnimatedImplementation.js
Original file line number Diff line number Diff line change
Expand Up @@ -556,7 +556,7 @@ type AnimatedNumeric =
| AnimatedAddition
| AnimatedDiffClamp
| AnimatedDivision
| AnimatedInterpolation
| AnimatedInterpolation<number>
| AnimatedModulo
| AnimatedMultiplication
| AnimatedSubtraction
Expand Down
4 changes: 3 additions & 1 deletion Libraries/Animated/NativeAnimatedHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,9 @@ function validateStyles(styles: {[key: string]: ?number, ...}): void {
}
}
function validateInterpolation(config: InterpolationConfigType): void {
function validateInterpolation<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): void {
for (const key in config) {
if (!isSupportedInterpolationParam(key)) {
throw new Error(
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Animated/animations/SpringAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ export type SpringAnimationConfig = {
...
}
| AnimatedColor
| AnimatedInterpolation,
| AnimatedInterpolation<number>,
overshootClamping?: boolean,
restDisplacementThreshold?: number,
restSpeedThreshold?: number,
Expand All @@ -67,7 +67,7 @@ export type SpringAnimationConfig = {

export type SpringAnimationConfigSingle = {
...AnimationConfig,
toValue: number | AnimatedValue | AnimatedInterpolation,
toValue: number,
overshootClamping?: boolean,
restDisplacementThreshold?: number,
restSpeedThreshold?: number,
Expand All @@ -90,7 +90,7 @@ class SpringAnimation extends Animation {
_startPosition: number;
_lastPosition: number;
_fromValue: number;
_toValue: any;
_toValue: number;
_stiffness: number;
_damping: number;
_mass: number;
Expand Down
6 changes: 3 additions & 3 deletions Libraries/Animated/animations/TimingAnimation.js
Original file line number Diff line number Diff line change
Expand Up @@ -36,15 +36,15 @@ export type TimingAnimationConfig = $ReadOnly<{
| AnimatedValueXY
| RgbaValue
| AnimatedColor
| AnimatedInterpolation,
| AnimatedInterpolation<number>,
easing?: (value: number) => number,
duration?: number,
delay?: number,
}>;

export type TimingAnimationConfigSingle = $ReadOnly<{
...AnimationConfig,
toValue: number | AnimatedValue | AnimatedInterpolation,
toValue: number,
easing?: (value: number) => number,
duration?: number,
delay?: number,
Expand All @@ -62,7 +62,7 @@ function easeInOut() {
class TimingAnimation extends Animation {
_startTime: number;
_fromValue: number;
_toValue: any;
_toValue: number;
_duration: number;
_delay: number;
_easing: (value: number) => number;
Expand Down
4 changes: 3 additions & 1 deletion Libraries/Animated/nodes/AnimatedAddition.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ class AnimatedAddition extends AnimatedWithChildren {
return this._a.__getValue() + this._b.__getValue();
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}

Expand Down
4 changes: 3 additions & 1 deletion Libraries/Animated/nodes/AnimatedDiffClamp.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ class AnimatedDiffClamp extends AnimatedWithChildren {
super.__makeNative(platformConfig);
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}

Expand Down
4 changes: 3 additions & 1 deletion Libraries/Animated/nodes/AnimatedDivision.js
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,9 @@ class AnimatedDivision extends AnimatedWithChildren {
return a / b;
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}

Expand Down
43 changes: 22 additions & 21 deletions Libraries/Animated/nodes/AnimatedInterpolation.js
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,9 @@ import type {PlatformConfig} from '../AnimatedPlatformConfig';

type ExtrapolateType = 'extend' | 'identity' | 'clamp';

export type InterpolationConfigType = $ReadOnly<{
export type InterpolationConfigType<OutputT: number | string> = $ReadOnly<{
inputRange: $ReadOnlyArray<number>,
outputRange: $ReadOnlyArray<number> | $ReadOnlyArray<string>,
outputRange: $ReadOnlyArray<OutputT>,
easing?: (input: number) => number,
extrapolate?: ExtrapolateType,
extrapolateLeft?: ExtrapolateType,
Expand All @@ -38,14 +38,14 @@ const linear = (t: number) => t;
* Very handy helper to map input ranges to output ranges with an easing
* function and custom behavior outside of the ranges.
*/
function createInterpolation(
config: InterpolationConfigType,
): (input: number) => number | string {
function createInterpolation<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): (input: number) => OutputT {
if (config.outputRange && typeof config.outputRange[0] === 'string') {
return createInterpolationFromStringOutputRange(config);
return (createInterpolationFromStringOutputRange((config: any)): any);
}

const outputRange: Array<number> = (config.outputRange: any);
const outputRange: $ReadOnlyArray<number> = (config.outputRange: any);

const inputRange = config.inputRange;

Expand Down Expand Up @@ -87,7 +87,7 @@ function createInterpolation(
);

const range = findRange(input, inputRange);
return interpolate(
return (interpolate(
input,
inputRange[range],
inputRange[range + 1],
Expand All @@ -96,7 +96,7 @@ function createInterpolation(
easing,
extrapolateLeft,
extrapolateRight,
);
): any);
};
}

Expand Down Expand Up @@ -195,7 +195,7 @@ const stringShapeRegex = /[+-]?(?:\d+\.?\d*|\.\d+)(?:[eE][+-]?\d+)?/g;
* -45deg // values with units
*/
function createInterpolationFromStringOutputRange(
config: InterpolationConfigType,
config: InterpolationConfigType<string>,
): (input: number) => string {
let outputRange: Array<string> = (config.outputRange: any);
invariant(outputRange.length >= 2, 'Bad output range');
Expand Down Expand Up @@ -299,17 +299,19 @@ function checkInfiniteRange(name: string, arr: $ReadOnlyArray<number>) {
);
}

class AnimatedInterpolation extends AnimatedWithChildren {
class AnimatedInterpolation<
OutputT: number | string,
> extends AnimatedWithChildren {
// Export for testing.
static __createInterpolation: (
config: InterpolationConfigType,
) => (input: number) => number | string = createInterpolation;
config: InterpolationConfigType<OutputT>,
) => (input: number) => OutputT = createInterpolation;

_parent: AnimatedNode;
_config: InterpolationConfigType;
_interpolation: (input: number) => number | string;
_config: InterpolationConfigType<OutputT>;
_interpolation: (input: number) => OutputT;

constructor(parent: AnimatedNode, config: InterpolationConfigType) {
constructor(parent: AnimatedNode, config: InterpolationConfigType<OutputT>) {
super();
this._parent = parent;
this._config = config;
Expand All @@ -330,7 +332,9 @@ class AnimatedInterpolation extends AnimatedWithChildren {
return this._interpolation(parentValue);
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<NewOutputT: number | string>(
config: InterpolationConfigType<NewOutputT>,
): AnimatedInterpolation<NewOutputT> {
return new AnimatedInterpolation(this, config);
}

Expand All @@ -343,7 +347,7 @@ class AnimatedInterpolation extends AnimatedWithChildren {
super.__detach();
}

__transformDataType(range: Array<any>): Array<any> {
__transformDataType(range: $ReadOnlyArray<OutputT>): Array<any> {
return range.map(NativeAnimatedHelper.transformDataType);
}

Expand All @@ -355,9 +359,6 @@ class AnimatedInterpolation extends AnimatedWithChildren {
return {
inputRange: this._config.inputRange,
// Only the `outputRange` can contain strings so we don't need to transform `inputRange` here
/* $FlowFixMe[incompatible-call] (>=0.38.0) - Flow error detected during
* the deployment of v0.38.0. To see the error, remove this comment and
* run flow */
outputRange: this.__transformDataType(this._config.outputRange),
extrapolateLeft:
this._config.extrapolateLeft || this._config.extrapolate || 'extend',
Expand Down
4 changes: 3 additions & 1 deletion Libraries/Animated/nodes/AnimatedModulo.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ class AnimatedModulo extends AnimatedWithChildren {
);
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}

Expand Down
5 changes: 3 additions & 2 deletions Libraries/Animated/nodes/AnimatedMultiplication.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,10 +38,11 @@ class AnimatedMultiplication extends AnimatedWithChildren {
return this._a.__getValue() * this._b.__getValue();
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}

__attach(): void {
this._a.__addChild(this);
this._b.__addChild(this);
Expand Down
4 changes: 3 additions & 1 deletion Libraries/Animated/nodes/AnimatedSubtraction.js
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,9 @@ class AnimatedSubtraction extends AnimatedWithChildren {
return this._a.__getValue() - this._b.__getValue();
}

interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}

Expand Down
4 changes: 3 additions & 1 deletion Libraries/Animated/nodes/AnimatedValue.js
Original file line number Diff line number Diff line change
Expand Up @@ -219,7 +219,9 @@ class AnimatedValue extends AnimatedWithChildren {
* Interpolates the value before updating the property, e.g. mapping 0-1 to
* 0-10.
*/
interpolate(config: InterpolationConfigType): AnimatedInterpolation {
interpolate<OutputT: number | string>(
config: InterpolationConfigType<OutputT>,
): AnimatedInterpolation<OutputT> {
return new AnimatedInterpolation(this, config);
}

Expand Down
6 changes: 3 additions & 3 deletions Libraries/Components/ScrollView/ScrollViewStickyHeader.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,7 +90,7 @@ class ScrollViewStickyHeader extends React.Component<Props, State> {
}

updateTranslateListener(
translateY: AnimatedImplementation.Interpolation,
translateY: AnimatedNode,
isFabric: boolean,
offset: AnimatedDiffClamp | null,
) {
Expand Down Expand Up @@ -274,11 +274,11 @@ class ScrollViewStickyHeader extends React.Component<Props, State> {
.interpolate({
extrapolateLeft: 'clamp',
inputRange: [layoutY, layoutY + 1],
outputRange: ([0, 1]: Array<number>),
outputRange: [0, 1],
})
.interpolate({
inputRange: [0, 1],
outputRange: ([0, -1]: Array<number>),
outputRange: [0, -1],
}),
-this.state.layoutHeight,
0,
Expand Down

0 comments on commit 7b86fa2

Please sign in to comment.