【Typescript技巧】如何从“参数类型”推导出合适的“返回类型”
问题描述
为下面描述的map函数添加一个更好的Typescript类型标注,提高使用者的类型安全性(如果不按下面的方式使用,就会报错):
map是一个用来做数组mapping的高阶函数,先接收mapping function(将【输入数组的项】映射为【输出数组的项】),然后接收输入数组,返回输出数组。
当mapping function没有传入时,mapping function默认为一个不做任何转化的函数(即x=>x
)。
用代码来表示,就是:
const map = (fn = x => x) => arr => {
const result = [];
for (const item of arr) {
result.push(fn(item));
}
return result;
};
方案1:函数重载
function map(): <V>(arr: V[]) => V[];
function map<V, R>(fn: (value: V) => R): (arr: V[]) => R[];
function map(fn = (x: any) => x) {
return (arr: any[]) => {
const result: any[] = [];
for (const item of arr) {
result.push(fn(item));
}
return result;
};
}
// 测试
const emptyMap = map();
const result1 = emptyMap([1, 2, 3]);
const result2 = emptyMap(["1", "2"]);
const mapNumberToString = map((v: number) => "str");
const result3 = mapNumberToString([1, 2, 3]);
// const result4 = mapNumberToString(["1", "2"]); // 报错,类型检查有效
// const mapNumberToString2 = map((v: number) => "str", [1, 2]); // 报错,类型检查有效
方案2:类型推导
事实上类型推导也能够做到(虽然可维护性下降很多),就当做是对Typescript类型推导的一个练习吧!
真实项目中遇到这种情况,应该选择可维护性更高的方式。
type FnBasic = (value: any) => any;
type InferV<Fn> = Fn extends ((value: infer V) => any) ? V : never;
type InferR<Fn> = Fn extends ((value: any) => infer R) ? R : never;
const map = <Args extends [FnBasic?]>(...args: Args) => {
// 通过tuple types来拿到可选参数的实际类型
// https://stackoverflow.com/a/51489032
type FnType = Args[0];
const fn: FnBasic = args[0] || (x => x);
type ReturnType = FnType extends (undefined)
? <V>(arr: V[]) => V[] // 返回一个带泛型的函数,从而V能够在被调用时确定
: (arr: InferV<FnType>[]) => InferR<FnType>[]; // V已经能够推导出来
const ret: ReturnType = ((arr: any[]) => {
const result = [];
for (const item of arr) {
result.push(fn(item));
}
return result;
}) as any;
return ret;
};
// 测试
const emptyMap = map();
const result1 = emptyMap([1, 2, 3]);
const result2 = emptyMap(["1", "2"]);
const mapNumberToString = map((v: number) => "str");
const result3 = mapNumberToString([1, 2, 3]);
// const result4 = mapNumberToString(["1", "2"]); // 报错,类型检查有效
// const mapNumberToString2 = map((v: number) => "str", [1, 2]); // 报错,类型检查有效
其中主要的知识点是:
- 利用tuple types来拿到可选参数的实际类型(如果不用这个方式,可选参数的类型很难用来做类型推导)
- 基于参数类型推导出想要的返回值类型(涉及到infer的使用)
csRyan的学习专栏
分享对于计算机科学的学习和思考,只发布有价值的文章: 对于那些网上已经有完整资料,且相关资料已经整...
So you're passionate? How passionate? What actions does your passion lead you to do? If the heart...
1.1k 声望
181 粉丝
推荐阅读
手写一个Parser - 代码简单而功能强大的Pratt Parsing
在编译的流程中,一个很重要的步骤是语法分析(又称解析,Parsing)。解析器(Parser)负责将Token流转化为抽象语法树(AST)。这篇文章介绍一种Parser的实现算法:Pratt Parsing,又称Top Down Operator Precede...
csRyan阅读 3k
过滤/筛选树节点
又是树,是我跟树杠上了吗?—— 不,是树的问题太多了!🔗 相关文章推荐:使用递归遍历并转换树形数据(以 TypeScript 为例)从列表生成树 (JavaScript/TypeScript) 过滤和筛选是一个意思,都是 filter。对于列表来...
边城赞 18阅读 7.7k评论 3
掌握 TypeScript:20 个提高代码质量的最佳实践
本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。更多开源作品请看 GitHub [链接] ,包含一线大厂面试完整考点、资料以及我的系列文章。
前端小智赞 9阅读 1.6k
不要用100vh做移动响应
本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。更多开源作品请看 GitHub [链接] ,包含一线大厂面试完整考点、资料以及我的系列文章。
前端小智赞 6阅读 461
为什么JSON.parse会损坏大数字,如何解决这个问题?
微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。本文 GitHub [链接] 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
前端小智赞 8阅读 904
作为前端,需要知道这些工具,解放我的重复劳动力
本文首发于微信公众号:大迁世界, 我的微信:qq449245884,我会第一时间和你分享前端行业趋势,学习途径等等。更多开源作品请看 GitHub [链接] ,包含一线大厂面试完整考点、资料以及我的系列文章。
前端小智赞 4阅读 492
5个async/await最佳实践
微信搜索 【大迁世界】, 我会第一时间和你分享前端行业趋势,学习途径等等。本文 GitHub [链接] 已收录,有一线大厂面试完整考点、资料以及我的系列文章。
前端小智赞 5阅读 793
So you're passionate? How passionate? What actions does your passion lead you to do? If the heart...
1.1k 声望
181 粉丝
宣传栏
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。