react-native-reanimated
Version:
More powerful alternative to Animated library for React Native.
158 lines (142 loc) • 4.91 kB
text/typescript
;
import { useEffect, useRef } from 'react';
import type { WorkletFunction } from 'react-native-worklets';
import { makeShareable } from 'react-native-worklets';
import { initialUpdaterRun } from '../animation';
import { logger } from '../common';
import type { AnimatedPropsAdapterWorklet } from '../commonTypes';
import { startMapper, stopMapper } from '../core';
import type { AnimatedProps } from '../createAnimatedComponent/commonTypes';
import { makeViewDescriptorsSet } from '../ViewDescriptorsSet';
import type {
AnimatedStyleHandle,
DefaultStyle,
DependencyList,
} from './commonTypes';
import type { AnimatedUpdaterData } from './useAnimatedStyleCommon';
import {
buildWorkletsHash,
checkSharedValueUsage,
styleUpdater,
} from './useAnimatedStyleCommon';
import { useSharedValue } from './useSharedValue';
import { validateAnimatedStyles } from './utils';
/**
* Lets you create a styles object, similar to StyleSheet styles, which can be
* animated using shared values.
*
* @param updater - A function returning an object with style properties you
* want to animate.
* @param dependencies - An optional array of dependencies.
* @returns An animated style object which has to be passed to the `style`
* property of an Animated component you want to animate.
* @see https://docs.swmansion.com/react-native-reanimated/docs/core/useAnimatedStyle
*/
// @ts-expect-error Public type definition which strips internals.
export function useAnimatedStyle<Style extends DefaultStyle>(
updater: () => Style,
dependencies?: DependencyList | null
): AnimatedStyleHandle<Style>;
export function useAnimatedStyle<Style extends DefaultStyle | AnimatedProps>(
updater:
| WorkletFunction<[], Style>
| ((() => Style) & Record<string, unknown>),
_dependencies?: DependencyList | null,
adapters?: AnimatedPropsAdapterWorklet | AnimatedPropsAdapterWorklet[] | null,
isAnimatedProps = false
): AnimatedStyleHandle<Style | AnimatedProps> {
if (__DEV__ && _dependencies !== undefined && _dependencies !== null) {
logger.warn('dependencies should only be used in web implementation.');
}
const animatedUpdaterData = useRef<AnimatedUpdaterData | null>(null);
const inputs = Object.values(updater.__closure ?? {});
const adaptersArray = adapters
? Array.isArray(adapters)
? adapters
: [adapters]
: [];
const adaptersHash = adapters ? buildWorkletsHash(adaptersArray) : null;
const areAnimationsActive = useSharedValue<boolean>(true);
// build dependencies
const dependencies = [...inputs, updater.__workletHash];
if (adaptersHash) {
dependencies.push(adaptersHash);
}
if (!animatedUpdaterData.current) {
const initialStyle = initialUpdaterRun(updater);
if (__DEV__) {
validateAnimatedStyles(initialStyle);
}
animatedUpdaterData.current = {
initial: {
value: initialStyle,
updater,
},
remoteState: makeShareable({
last: initialStyle,
animations: {},
isAnimationCancelled: false,
isAnimationRunning: false,
}),
viewDescriptors: makeViewDescriptorsSet(),
styleUpdaterContainer: { current: undefined },
};
}
const { initial, remoteState, viewDescriptors } = animatedUpdaterData.current;
const shareableViewDescriptors = viewDescriptors.shareableViewDescriptors;
dependencies.push(shareableViewDescriptors);
useEffect(() => {
let updaterFn = updater;
if (adapters) {
updaterFn = (() => {
'worklet';
const newValues = updater();
adaptersArray.forEach((adapter) => {
adapter(newValues as Record<string, unknown>);
});
return newValues;
}) as WorkletFunction<[], Style>;
}
const fun = (forceUpdate?: boolean) => {
'worklet';
styleUpdater(
shareableViewDescriptors,
updaterFn,
remoteState,
areAnimationsActive,
isAnimatedProps,
forceUpdate
);
};
if (animatedUpdaterData.current) {
animatedUpdaterData.current.styleUpdaterContainer.current = fun;
}
const mapperId = startMapper(fun, inputs);
return () => {
stopMapper(mapperId);
};
// eslint-disable-next-line react-hooks/exhaustive-deps
}, dependencies);
useEffect(() => {
areAnimationsActive.value = true;
return () => {
areAnimationsActive.value = false;
};
}, [areAnimationsActive]);
if (__DEV__) {
checkSharedValueUsage(initial.value);
}
const animatedStyleHandle = useRef<AnimatedStyleHandle<
Style | AnimatedProps
> | null>(null);
if (!animatedStyleHandle.current) {
const styleUpdaterContainer =
animatedUpdaterData.current.styleUpdaterContainer;
animatedStyleHandle.current = {
viewDescriptors,
initial,
styleUpdaterContainer,
};
}
return animatedStyleHandle.current;
}