本文的翻译于<<Effective TypeScript>>, 特别感谢!! ps: 本文会用简洁, 易懂的语言描述原书的所有要点. 如果能看懂这文章,将节省许多阅读时间. 如果看不懂,务必给我留言, 我回去修改.
技巧40:将不安全的断言藏在类型安全的函数
有些函数类型声明很简单,但是用类型安全的方法实现却很难。这个时候很多人可能会考虑使用类型断言。当不得不用类型断言的时候,与其让断言到处散落,最好是让将不安全的断言藏在类型安全的函数。
假定你想缓存函数最后的调用结果,这是一个常用的技术用于react,用于节省函数频繁调用的资源。我们可以写一个 cacheLast 高阶函数能给函数添加缓存行为:
declare function cacheLast<T extends Function>(fn: T): T;
这是其中的一个实现方法:
function cacheLast<T extends Function>(fn: T): T {
let lastArgs: any[]|null = null;
let lastResult: any;
return function(...args: any[]) {
// ~~~~~~~~~~~~~~~~~~~~~~~~~~
// Type '(...args: any[]) => any' is not assignable to type 'T'
if (!lastArgs || !shallowEqual(lastArgs, args)) {
lastResult = fn(...args);
lastArgs = args;
}
return lastResult;
};
}
这个报错很合理:因为ts无法相信返回的函数和T有什么关系。但是args传入会通过正确的类型检查,返回值lastResult也类型正确。我们在这添加断言没有危害。
如果我们这样添加断言:
function cacheLast<T extends Function>(fn: T): T {
let lastArgs: any[]|null = null;
let lastResult: any;
return function(...args: any[]) {
if (!lastArgs || !shallowEqual(lastArgs, args)) {
lastResult = fn(...args);
lastArgs = args;
}
return lastResult;
} as unknown as T;
}
这样能通过检查,但是隐藏了很多类型。有个问题:
不会检查连续调用的几个值是否相同,如果原始函数有一些定义好的属性,那么包装函数就不会有这些属性。因此就没有相同类型。
我们可以对shallowEqual函数做一下修改,如果shallowEqual比较的是两个array很简单。我们这里假定比较两个object。:
declare function shallowObjectEqual<T extends object>(a: T, b: T): boolean;
实现如下:
function shallowObjectEqual<T extends object>(a: T, b: T): boolean {
for (const [k, aVal] of Object.entries(a)) {
if (!(k in b) || aVal !== b[k]) {
// ~~~~ Element implicitly has an 'any' type
// because type '{}' has no index signature
return false;
}
}
return
Object.keys(a).length === Object.keys(b).length;
}
这个错误让人有点难以理解,因为你明明已经检查了 k in b, 但是还是报错: b[k] 无法获取。所以只能这样做:
function shallowObjectEqual<T extends object>(a: T, b: T): boolean {
for (const [k, aVal] of Object.entries(a)) {
if (!(k in b) || aVal !== (b as any)[k]) {
return false;
}
}
return Object.keys(a).length === Object.keys(b).length;
}
这样的断言没有危害,因为你检查了 k in b。函数的类型声明也正确。