React的useCallback
是一个函数的包装器,它返回与输入相同的类型,这里是TS类型:
function useCallback<T extends (...args: any[]) => any>(
callback: T,
deps: DependencyList,
): T;
如果我没有为输入函数指定参数类型,我希望noImplicitAny
会导致错误。但是,这不会导致错误:
const fn = useCallback(arg => {}, []);
可以将useCallback的类型更改为this,以在大多数情况下,当参数的类型未指定时触发错误:
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
类型来解决这些问题?
让我们来看看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
是底部类型。不允许为never
分配任何/any
类型。考虑这个例子:
let x: never;
let y: any;
x = y; // error
never
可赋值给所有对象,反之则不然。请参阅文档
never类型是所有类型的子类型,且可赋值给所有类型;然而,没有类型是never的子类型或可赋值的类型(除了never本身)。即使any也不能赋值给never。
Byin most cases when the argument's type isn't specified
我假设你想禁止使用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>
可能是一个比我更好的解决方案。