mirror of
https://github.com/facebook/react.git
synced 2025-11-01 09:12:30 +00:00
Add type defs for reanimated
The reanimated babel plugin specifically looks for args to their hooks that are
callbacks, then it workletizes the body of that callback so it can run on the
main thread.
But, forget extracts that callback into a temporary variable and then replaces
the previously inlined callback as an identifier, so that breaks reanimated's
babel plugin. so what happens is some of the previously workletized functions no
longer do after forget runs, which throws a runtime error about a non-worklet
function running on the main thread.
Reanimated expects this: ``` const animatedGProps = useAnimatedProp(function ()
{ ... }) ```
But forget does this: ``` const t0 =function () { ... } const animatedGProps =
useAnimatedProp(t0) ```
With the type definitions, Forget no longer assumes the args to reanimated APIs
escape so Forget does not memoize and they stay as is.
This commit is contained in:
@@ -17,6 +17,7 @@ import {
|
||||
DEFAULT_SHAPES,
|
||||
Global,
|
||||
GlobalRegistry,
|
||||
installReAnimatedTypes,
|
||||
} from "./Globals";
|
||||
import {
|
||||
BlockId,
|
||||
@@ -357,6 +358,16 @@ const EnvironmentConfigSchema = z.object({
|
||||
*/
|
||||
enableTreatFunctionDepsAsConditional: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* The react native re-animated library uses custom Babel transforms that
|
||||
* requires the calls to library API remain unmodified.
|
||||
*
|
||||
* If this flag is turned on, the React compiler will use custom type
|
||||
* definitions for reanimated library to make it's Babel plugin work
|
||||
* with the compiler.
|
||||
*/
|
||||
enableCustomTypeDefinitionForReAnimated: z.boolean().default(false),
|
||||
|
||||
/**
|
||||
* If specified, this value is used as a pattern for determing which global values should be
|
||||
* treated as hooks. The pattern should have a single capture group, which will be used as
|
||||
@@ -469,6 +480,10 @@ export class Environment {
|
||||
);
|
||||
}
|
||||
|
||||
if (config.enableCustomTypeDefinitionForReAnimated) {
|
||||
installReAnimatedTypes(this.#globals, this.#shapes);
|
||||
}
|
||||
|
||||
this.#contextIdentifiers = contextIdentifiers;
|
||||
this.#hoistedIdentifiers = new Set();
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import { Effect, ValueKind, ValueReason } from "./HIR";
|
||||
import {
|
||||
BUILTIN_SHAPES,
|
||||
BuiltInArrayId,
|
||||
BuiltInMixedReadonlyId,
|
||||
BuiltInUseEffectHookId,
|
||||
BuiltInUseInsertionEffectHookId,
|
||||
BuiltInUseLayoutEffectHookId,
|
||||
@@ -413,3 +414,76 @@ DEFAULT_GLOBALS.set(
|
||||
"globalThis",
|
||||
addObject(DEFAULT_SHAPES, "globalThis", TYPED_GLOBALS)
|
||||
);
|
||||
|
||||
export function installReAnimatedTypes(
|
||||
globals: GlobalRegistry,
|
||||
registry: ShapeRegistry
|
||||
): void {
|
||||
// hooks that freeze args and return frozen value
|
||||
const frozenHooks = [
|
||||
"useFrameCallback",
|
||||
"useAnimatedStyle",
|
||||
"useAnimatedProps",
|
||||
"useAnimatedScrollHandler",
|
||||
"useAnimatedReaction",
|
||||
"useWorkletCallback",
|
||||
];
|
||||
for (const hook of frozenHooks) {
|
||||
globals.set(
|
||||
hook,
|
||||
addHook(registry, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: { kind: "Object", shapeId: BuiltInMixedReadonlyId },
|
||||
returnValueKind: ValueKind.Frozen,
|
||||
noAlias: true,
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: "Custom",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* hooks that return a mutable value. ideally these should be modelled as a
|
||||
* ref, but this works for now.
|
||||
*/
|
||||
const mutableHooks = ["useSharedValue", "useDerivedValue"];
|
||||
for (const hook of mutableHooks) {
|
||||
globals.set(
|
||||
hook,
|
||||
addHook(registry, {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Freeze,
|
||||
returnType: { kind: "Poly" },
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
calleeEffect: Effect.Read,
|
||||
hookKind: "Custom",
|
||||
})
|
||||
);
|
||||
}
|
||||
|
||||
// functions that return mutable value
|
||||
const funcs = [
|
||||
"withTiming",
|
||||
"withSpring",
|
||||
"createAnimatedPropAdapter",
|
||||
"withDecay",
|
||||
"withRepeat",
|
||||
"runOnUI",
|
||||
"executeOnUIRuntimeSync",
|
||||
];
|
||||
for (const fn of funcs) {
|
||||
globals.set(
|
||||
fn,
|
||||
addFunction(registry, [], {
|
||||
positionalParams: [],
|
||||
restParam: Effect.Read,
|
||||
returnType: { kind: "Poly" },
|
||||
calleeEffect: Effect.Read,
|
||||
returnValueKind: ValueKind.Mutable,
|
||||
noAlias: true,
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
+76
@@ -0,0 +1,76 @@
|
||||
|
||||
## Input
|
||||
|
||||
```javascript
|
||||
// @enableCustomTypeDefinitionForReAnimated
|
||||
function Component() {
|
||||
const radius = useSharedValue(50);
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
// draw a circle
|
||||
const path = `
|
||||
M 100, 100
|
||||
m -${radius.value}, 0
|
||||
a ${radius.value},${radius.value} 0 1,0 ${radius.value * 2},0
|
||||
a ${radius.value},${radius.value} 0 1,0 ${-radius.value * 2},0
|
||||
`;
|
||||
return {
|
||||
d: path,
|
||||
};
|
||||
});
|
||||
|
||||
// attach animated props to an SVG path using animatedProps
|
||||
return (
|
||||
<Svg>
|
||||
<AnimatedPath animatedProps={animatedProps} fill="black" />
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
## Code
|
||||
|
||||
```javascript
|
||||
import { unstable_useMemoCache as useMemoCache } from "react"; // @enableCustomTypeDefinitionForReAnimated
|
||||
function Component() {
|
||||
const $ = useMemoCache(2);
|
||||
const radius = useSharedValue(50);
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
const path = `
|
||||
M 100, 100
|
||||
m -${radius.value}, 0
|
||||
a ${radius.value},${radius.value} 0 1,0 ${radius.value * 2},0
|
||||
a ${radius.value},${radius.value} 0 1,0 ${-radius.value * 2},0
|
||||
`;
|
||||
return { d: path };
|
||||
});
|
||||
let t0;
|
||||
if ($[0] !== animatedProps) {
|
||||
t0 = (
|
||||
<Svg>
|
||||
<AnimatedPath animatedProps={animatedProps} fill="black" />
|
||||
</Svg>
|
||||
);
|
||||
$[0] = animatedProps;
|
||||
$[1] = t0;
|
||||
} else {
|
||||
t0 = $[1];
|
||||
}
|
||||
return t0;
|
||||
}
|
||||
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
isComponent: false,
|
||||
};
|
||||
|
||||
```
|
||||
|
||||
+29
@@ -0,0 +1,29 @@
|
||||
// @enableCustomTypeDefinitionForReAnimated
|
||||
function Component() {
|
||||
const radius = useSharedValue(50);
|
||||
|
||||
const animatedProps = useAnimatedProps(() => {
|
||||
// draw a circle
|
||||
const path = `
|
||||
M 100, 100
|
||||
m -${radius.value}, 0
|
||||
a ${radius.value},${radius.value} 0 1,0 ${radius.value * 2},0
|
||||
a ${radius.value},${radius.value} 0 1,0 ${-radius.value * 2},0
|
||||
`;
|
||||
return {
|
||||
d: path,
|
||||
};
|
||||
});
|
||||
|
||||
// attach animated props to an SVG path using animatedProps
|
||||
return (
|
||||
<Svg>
|
||||
<AnimatedPath animatedProps={animatedProps} fill="black" />
|
||||
</Svg>
|
||||
);
|
||||
}
|
||||
export const FIXTURE_ENTRYPOINT = {
|
||||
fn: Component,
|
||||
params: [],
|
||||
isComponent: false,
|
||||
};
|
||||
@@ -490,6 +490,7 @@ const skipFilter = new Set([
|
||||
"fbt/fbt-preserve-jsxtext",
|
||||
"todo.useContext-mutate-context-in-callback",
|
||||
"loop-unused-let",
|
||||
"reanimated-no-memo-arg",
|
||||
|
||||
// Tested e2e in forget-feedback repo
|
||||
"userspace-use-memo-cache",
|
||||
|
||||
Reference in New Issue
Block a user