javascript - 为使用 useCallback 包装的函数启用 noImplicitAny?
React 的 useCallback 是一个函数的包装器,它返回与输入相同类型的值,下面是 TypeScript 类型:
function useCallback<T extends (...args: any[]) => any>(
callback: T,
deps: DependencyList,
): T;
如果我没有为输入函数指定参数类型,我期望 noImplicitAny 会引发错误。然而,这并不会引发错误:
const fn = useCallback(arg => {}, []);
可以将 useCallback 的类型更改为此类型,以在大多数情况下当参数类型未指定时触发错误:
function useCallback<T extends (...args: never[]) => unknown>(
callback: T,
deps: DependencyList,
): T;
const fn = useCallback(arg => arg.toString()); // Property 'toString' does not exist on type 'never'.
const fn = useCallback((arg: number) => arg.toString()); // No errors
然而,这在某些情况下似乎会有问题。例如,使用默认参数时:
const fn = useCallback((arg = 0) => arg.toString()); // Type 'number' is not assignable to type 'never'.
通常,TS 会推断 arg 的类型。如果我手动指定 arg 的类型,那么 Eslint 的 @typescript-eslint/no-inferrable-types 规则会报错。
有没有一种更好的 useCallback 类型来解决这些问题?
答案#1
让我们来看一下 useCallback 的类型:
/**
* `useCallback` will return a memoized version of the callback that only changes if one of the `inputs`
* has changed.
*
* @version 16.8.0
* @see https://reactjs.org/docs/hooks-reference.html#usecallback
*/
// TODO (TypeScript 3.0): <T extends (...args: never[]) => unknown>
function useCallback<T extends (...args: any[]) => any>(callback: T, deps: DependencyList): T;
正如你可能已经注意到的,callback 是 (...args: any[]) => any 的子类型。这意味着 any 已经被明确提供给 args。这就是为什么在这种情况下 noImplicitAny 不起作用,因为 any 是明确的。根据我的理解,以及你注意到的——React 团队希望用 (...args: never[]) => unknown 来替换当前的 callback 类型。
此子类型为回调参数提供 never 类型。
useCallback((arg) => arg.toString(), []); // error because [arg] is never
arg=0 在 never 的情况下不起作用,因为 never 是底类型。你不被允许将任何/any 类型赋值给 never。请考虑这个例子:
let x: never;
let y: any;
x = y; // error
never 可以赋值给所有类型,但反过来不行。请查看文档
never 类型是每种类型的子类型,并且可以赋值给每种类型;然而,没有任何类型是 never 的子类型,也不能赋值给 never(除了 never 本身)。甚至 any 也不能赋值给 never。
我猜测你在说的 在大多数情况下,当参数的类型未指定时 是想禁止使用 any。我不确定 React 团队是否能提供这样的限制,因为很多人在从 js 迁移到 ts 时都在使用 any。
如果你想使用默认参数,参数的类型应该是 any 或者与默认参数类型相同。因此,我们有一个冲突。你想禁止使用 any,同时又想使用默认参数。
你可以通过这种方式覆盖 useCallback 的类型:
// credits goes to https://stackoverflow.com/questions/55541275/typescript-check-for-the-any-type
type IfAny<T, Y, N> = 0 extends 1 & T ? Y : N;
type IsAny<T> = IfAny<T, true, false>;
type ValidateArguments<T extends any[]> = IsAny<
T[number]
> extends true
? 'Custom Error'[] // please provide any type you want
: T;
type HandleCallback<T extends (...args: any[]) => any> = (
...args: ValidateArguments<Parameters<T>>
) => ReturnType<T>;
declare module "react" {
function useCallback<T extends (...args: any[]) => any>(
callback: HandleCallback<T>,
deps: any[]
): T;
}
const App = () => {
const result = useCallback((arg /** "Custom Error" */) => {}, []);
};
操场
除了 CustomError,你可以使用任何你想要的类型。然而,使用 <T extends (...args: never[]) => unknown> 可能比我的方案更好。
