巧用 TypeScript(四)
用 Decorator 限制类型
Decorator 可用于限制类方法的返回类型,如下所示:
const TestDecorator = () => {return (target: Object,key: string | symbol,descriptor: TypedPropertyDescriptor<() => number> // 函数返回值必须是 number) => {// 其他代码}
}class Test {@TestDecorator()testMethod() {return '123'; // Error: Type 'string' is not assignable to type 'number'}
}
复制代码
你也可以用泛型让 TestDecorator
的传入参数类型与 testMethod
的返回参数类型兼容:
const TestDecorator = <T>(para: T) => {return (target: Object,key: string | symbol,descriptor: TypedPropertyDescriptor<() => T>) => {// 其他代码}
}class Test {@TestDecorator('hello')testMethod() {return 123; // Error: Type 'number' is not assignable to type 'string'}
}
复制代码
泛型的类型推断
在定义泛型后,有两种方式使用,一种是传入泛型类型,另一种使用类型推断,即编译器根据其他参数类型来推断泛型类型。简单示例如下:
declare function fn<T>(arg: T): T; // 定义一个泛型函数const fn1 = fn<string>('hello'); // 第一种方式,传入泛型类型 string
const fn2 = fn(1); // 第二种方式,从参数 arg 传入的类型 number,来推断出泛型 T 的类型是 number
复制代码
它通常与映射类型一起使用,用来实现一些比较复杂的功能。
Vue Type 简单实现
如下一个例子:
type Options<T> = {[P in keyof T]: T[P];
}declare function test<T>(o: Options<T>): T;test({ name: 'Hello' }).name // string
复制代码
test
函数将传入参数的所有属性取出来,现在我们来一步一步加工,实现想要的功能。
首先,更改传入参数的形式,由 { name: 'Hello' }
的形式变更为 { data: { name: 'Hello' } }
,调用函数的返回值类型不变,即 test({ data: { name: 'Hello' } }).name
的值也是 string 类型。
这并不复杂,这只需要把传入参数的 data
类型设置为 T 即可:
declare function test<T>(o: { data: Options<T> }): T;test({data: { name: 'Hello' }}).name // string
复制代码
当 data
对象里,含有函数时,它也能运作:
const param = {data: {name: 'Hello',someMethod() {return 'hello world'}}
}test(param).someMethod() // string
复制代码
接着,考虑一种特殊的函数情景,像 Vue 中 Computed 一样,不调用函数,也能取出函数的返回值类型。现在传入参数的形式变更为:
const param = {data: {name: 'Hello'},computed: {age() {return 20;}}
}
复制代码
一个函数的类型可以简单的看成是 () => T
的形式,对象中的方法类型,可以看成 a: () => T
的形式,在反向推导时(由函数返回值,来推断类型 a
的类型),可以利用它,现在,需要添加一个映射类型 Computed<T>
,用来处理 computed
里的函数:
type Options<T> = {[P in keyof T]: T[P]
}type Computed<T> = {[P in keyof T]: () => T[P]
}interface Params<T, M> {data: Options<T>;computed: Computed<M>;
}declare function test<T, M>(o: Params<T, M>): T & M;const param = {data: {name: 'Hello'},computed: {age() {return 20}}
}test(param).name // string
test(param).age // number
复制代码
最后,结合巧用 TypeScript(一) 中提到的 ThisType
映射类型,可以轻松的实现在 computed age 方法下访问 data 中的数据:
type Options<T> = {[P in keyof T]: T[P]
}type Computed<T> = {[P in keyof T]: () => T[P]
}interface Params<T, M> {data: Options<T>;computed: Computed<M>;
}declare function test<T, M>(o: Params<T, M> & ThisType<T & M>): T & M;test({data: {name: 'Hello'},computed: {age() {this.name; // stringreturn 20;}}
})
复制代码
至此,只有 data, computed 简单版的 Vue Type 已经实现。
扁平数组构建树形结构
扁平数组构建树形结构即是将一组扁平数组,根据 parent_id(或者是其他)转换成树形结构:
// 转换前数据
const arr = [{ id: 1, parentId: 0, name: 'test1'},{ id: 2, parentId: 1, name: 'test2'},{ id: 3, parentId: 0, name: 'test3'}
];// 转化后
[{id: 1,parentId: 0,name: 'test1',children: [{ id: 2, parentId: 1, name: 'test2', children: [] }]},{id: 3,parentId: 0,name: 'test3',children: []}
]
复制代码
如果 children 字段名字不变,函数的类型并不难写,它大概是如下样子:
interface Item {id: number;parentId: number;name: string;
}type TreeItem = Item & { children: TreeItem[] | [] };declare function listToTree(list: Item[]): TreeItem[];listToTree(arr).forEach(i => i.children) // ok
复制代码
但是在很多时候,children 字段的名字并不固定,而是从参数中传进来:
const options = {childrenKey: 'childrenList'
}listToTree(arr, options);
复制代码
此时,children
字段名称,应该为 childrenList
:
[{id: 1,parentId: 0,name: 'test1',childrenList: [{ id: 2, parentId: 1, name: 'test2', childrenList: [] }]},{id: 3,parentId: 0,name: 'test3',childrenList: []}
]
复制代码
实现的思路大致是前文所说的利用泛型的类型推断,从传入的 options 参数中,得到 childrenKey
的类型,然后再传给 TreeItem
,如下:
interface Options<T extends string> { // 限制为 string 类型childrenKey: T;
}declare function listToTree<T extends string = 'children'>(list: Item[], options: Options<T>): TreeItem<T>[];
复制代码
当 options 为 { childrenKey: 'childrenList' }
时,T 能被正确推导出为 childrenList
。接着,只需要在 TreeItem
中,把 children
修改为传入的 T 即可:
interface Item {id: number;parentId: number;name: string;
}interface Options<T extends string> {childrenKey: T;
}type TreeItem<T extends string> = Item & { [key in T]: TreeItem<T>[] | [] };declare function listToTree<T extends string = 'children'>(list: Item[], options: Options<T>): TreeItem<T>[];listToTree(arr, { childrenKey: 'childrenList' }).forEach(i => i.childrenList) // ok
复制代码
有一点局限性,由于对象字面量的 Fresh 的影响,当 options 不是以对象字面量的形式传入时,需要给它断言:
const options = {childrenKey: 'childrenList' as 'childrenList'
}listToTree(arr, options).forEach(i => i.childrenList) // ok
复制代码
更多
- 巧用 TypeScript(三)
- 巧用 TypeScript(二)
- 巧用 TypeScript(一)
- 深入理解 TypeScript
巧用 TypeScript(四)相关推荐
- 巧用 TypeScript Literal Types 模拟枚举类型
看下面这个例子: let x: "hello" = "hello"; // OK x = "hello"; // ... x = " ...
- 来用 TypeScript(技术周刊 2019-04-01)
前端快爆 WebKit 已经实现了 ResizeObserver API,此前该 API 已被 Chrome 支持.通过 ResizeObserver 可以监听元素盒子尺寸的变化.? 点评:随着 Ed ...
- 冰雪奇缘,白色世界:四个IT人的四姑娘山双桥沟游记
去年9月初去了川西的稻城亚丁,体会了金色世界秋日童话,还写了一篇游记<从你的全世界路过-一群程序员的稻城亚丁游记>,也是得到了很多朋友和童鞋的点赞.今年11月初趁着周末的两天时间和朋友去了 ...
- 一文教你如何学习TypeScript
在看这篇文章之前,我是强烈推荐TypeScript 入门教程这本书的.因为这本书它是:从 JavaScript 程序员的角度总结思考,循序渐进的理解TypeScript.文章来源也是该书,但听我一句话 ...
- ❤️表弟说看了这本书后,他的TypeScript技术已经登峰造极了!❤️
在看这篇文章之前,我是强烈推荐TypeScript 入门教程这本书的.因为这本书它是:从 JavaScript 程序员的角度总结思考,循序渐进的理解TypeScript.文章来源也是该书,但听我一句话 ...
- JetBrains 开发者调查 - 编程语言趋势
几个月前在公众号里发布了 StackOverflow 2020 开发者调查结果,其结果对 .NET Core 很友好. 今天我们看看 JetBrains 2017-2020 四年的开发者调查结果统计, ...
- .NET和Java之争
这几天连续有多篇文章诋毁.NET,这类文章我十几年前就看得多了,只不过十几年前是C和C++之争,C++和Java之争.我从来不理这类文章,因为这类口水战并没有什么实际意义. 然而接连收到多位粉丝私聊说 ...
- 对小程序框架WePY的精简总结
大家下午好,萍子最近一直都在写小程序的项目,其中也涉及到了小程序的框架--WePY,讲真的,一开始对这个框架并没有很熟悉,所以也是看了多次它的对应文档,然后特此整理下来,写成博文保存一下,方便日后查看 ...
- ApacheCN Asp.NET 译文集 20211126 更新
ASP.NET Core2 基础知识 零.前言 一.搭建舞台 二.控制器 三.视图 四.模型 五.验证 六.路由 七.RestBuy 八.添加功能.测试和部署 ASP.NET Core3 和 Angu ...
- ApacheCN React 译文集 20211118 更新
React 入门手册 零.前言 一.React 和 UI 设计简介 二.创建组件 三.管理用户交互 React 全栈项目 零.前言 一.使用 MERN 释放 React 应用 二.准备开发环境 三.使 ...
最新文章
- poj3468 A Simple Problem with Integers
- 记录一次生产环境下的jvm内存泄露问题和分析解决过程!
- 高性能WEB开发(6) - web性能测试工具推荐
- 超图桌面版区分不同类型数据源的图标
- 给计算机系统的资产分配的记号被称为什么,哈工大2015计算机复试试题(25页)-原创力文档...
- Java单元测试技巧之PowerMock
- 医学图像数据集和处理工具【总结】
- pythonsparkfilter_python中的map、filter、reduce函数
- 第一期_内存管理单元MMU
- Vue 下载本地静态资源
- 浏览器对象模型BOM、文档对象模型DOM
- ubuntu 12.04下Trackpoint 小红点灵敏度和速度调整
- 电脑上面的word文档被删除了怎么办?分享四种亲测恢复方法
- Windows安装 choco包管理工具
- HDU 6638 Snowy Smile 线段树+最大子段和
- 哪款游戏蓝牙耳机好用?好用的游戏蓝牙耳机推荐
- Python数据分析辅助审计工作
- Kubernetes 1.25 正式发布,多方面重大突破
- Linux-Centos7防火墙配置
- 串口通信是先发低位再发高位
热门文章
- 《linux核心应用命令速查》连载二:lastcomm:显示以前使用过的命令的信息
- WEB开发新势力——Openparty
- 二十一天学通C++之使用try/catch捕获异常
- 《Joel On Software》读后
- 【机器学习】xgboost以及lightgbm资料汇总
- 树莓派桌面没有时间_树莓派3B/3B+开启手机远程桌面和终端,没有屏幕和电脑的伙伴们有福啦!...
- centos安装apache+mysql_CentOS7安装apache+mysql+php环境
- 数据结构和算法liuyubobo_C++,java算法与数据结构-某课网价值166元实战教程
- python打包后怎么安装_别再问我怎么Python打包成exe了!
- python斜率转换为航向0-360_机器学习模型之LinearRegression(Python学习笔记)