Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: Add
worklet()
func that throws if value is not a worklet (#187)
* Add `worklet()` func that throws if value is not a worklet * fix: Fix `isWorklet` check * fix: Use `worklet` in `useWorklet`, and remove dependencylist * fix: Better error
- Loading branch information
Showing
5 changed files
with
121 additions
and
20 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,39 +1,33 @@ | ||
import { DependencyList, useMemo } from "react"; | ||
import type { IWorkletContext } from "src/types"; | ||
import { useMemo } from "react"; | ||
import type { IWorkletContext } from "../types"; | ||
import { worklet } from "../worklet"; | ||
|
||
/** | ||
* Create a Worklet function that persists between re-renders. | ||
* Create a Worklet function that automatically memoizes itself using it's auto-captured closure. | ||
* The returned function can be called from both a Worklet context and the JS context, but will execute on a Worklet context. | ||
* | ||
* @worklet | ||
* @param context The context to run this Worklet in. Can be `default` to use the default background context, or a custom context. | ||
* @param callback The Worklet. Must be marked with the `'worklet'` directive. | ||
* @param dependencyList The React dependencies of this Worklet. | ||
* @returns A memoized Worklet | ||
* ```ts | ||
* const sayHello = useWorklet('default', (name: string) => { | ||
* 'worklet' | ||
* console.log(`Hello ${name}, I am running on the Worklet Thread!`) | ||
* }, []) | ||
* }) | ||
* sayHello() | ||
* ``` | ||
*/ | ||
export function useWorklet<T extends (...args: any[]) => any>( | ||
context: IWorkletContext | "default", | ||
callback: T, | ||
dependencyList: DependencyList | ||
callback: T | ||
): (...args: Parameters<T>) => Promise<ReturnType<T>> { | ||
const worklet = useMemo( | ||
() => { | ||
if (context === "default") { | ||
return Worklets.defaultContext.createRunAsync(callback); | ||
} else { | ||
return context.createRunAsync(callback); | ||
} | ||
}, | ||
const func = worklet(callback); | ||
const ctx = context === "default" ? Worklets.defaultContext : context; | ||
|
||
return useMemo( | ||
() => ctx.createRunAsync(func), | ||
// eslint-disable-next-line react-hooks/exhaustive-deps | ||
dependencyList | ||
[...Object.values(func.__closure), ctx] | ||
); | ||
|
||
return worklet; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
import "./NativeWorklets"; | ||
export * from "./types"; | ||
export * from "./worklet"; | ||
export * from "./hooks/useRunOnJS"; | ||
export * from "./hooks/useSharedValue"; | ||
export * from "./hooks/useWorklet"; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,78 @@ | ||
type AnyFunc = (...args: any[]) => any; | ||
|
||
type Workletize<TFunc extends () => any> = TFunc & { | ||
__closure: Record<string, unknown>; | ||
__initData: { | ||
code: string; | ||
location: string; | ||
__sourceMap: string; | ||
}; | ||
__workletHash: number; | ||
}; | ||
const EXPECTED_KEYS: (keyof Workletize<AnyFunc>)[] = [ | ||
"__closure", | ||
"__initData", | ||
"__workletHash", | ||
]; | ||
|
||
/** | ||
* Checks whether the given function is a Worklet or not. | ||
*/ | ||
export function isWorklet<TFunc extends AnyFunc>( | ||
func: TFunc | ||
): func is Workletize<TFunc> { | ||
const maybeWorklet = func as Partial<Workletize<TFunc>> & TFunc; | ||
if (typeof maybeWorklet.__workletHash !== "number") return false; | ||
|
||
if ( | ||
maybeWorklet.__closure == null || | ||
typeof maybeWorklet.__closure !== "object" | ||
) | ||
return false; | ||
|
||
const initData = maybeWorklet.__initData; | ||
if (initData == null || typeof initData !== "object") return false; | ||
|
||
if ( | ||
typeof initData.__sourceMap !== "string" || | ||
typeof initData.code !== "string" || | ||
typeof initData.location !== "string" | ||
) | ||
return false; | ||
|
||
return true; | ||
} | ||
|
||
class NotAWorkletError<TFunc extends AnyFunc> extends Error { | ||
constructor(func: TFunc) { | ||
let funcName = func.name; | ||
if (funcName.length === 0) { | ||
funcName = func.toString(); | ||
} | ||
|
||
const expected = `[${EXPECTED_KEYS.join(", ")}]`; | ||
const received = `[${Object.keys(func).join(", ")}]`; | ||
super( | ||
`The function "${funcName}" is not a Worklet! \n` + | ||
`- Make sure the function "${funcName}" is decorated with the 'worklet' directive! \n` + | ||
`- Make sure react-native-worklets-core is installed properly! \n` + | ||
`- Make sure to add the react-native-worklets-core babel plugin to your babel.config.js! \n` + | ||
`- Make sure that no other plugin overrides the react-native-worklets-core babel plugin! \n` + | ||
`Expected "${funcName}" to contain ${expected}, but "${funcName}" only has these properties: ${received}` | ||
); | ||
} | ||
} | ||
|
||
/** | ||
* Ensures the given function is a Worklet, and throws an error if not. | ||
* @param func The function that should be a Worklet. | ||
* @returns The same function that was passed in. | ||
*/ | ||
export function worklet<TFunc extends () => any>( | ||
func: TFunc | ||
): Workletize<TFunc> { | ||
if (!isWorklet(func)) { | ||
throw new NotAWorkletError(func); | ||
} | ||
return func; | ||
} |