用 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(四)相关推荐

  1. 巧用 TypeScript Literal Types 模拟枚举类型

    看下面这个例子: let x: "hello" = "hello"; // OK x = "hello"; // ... x = " ...

  2. 来用 TypeScript(技术周刊 2019-04-01)

    前端快爆 WebKit 已经实现了 ResizeObserver API,此前该 API 已被 Chrome 支持.通过 ResizeObserver 可以监听元素盒子尺寸的变化.? 点评:随着 Ed ...

  3. 冰雪奇缘,白色世界:四个IT人的四姑娘山双桥沟游记

    去年9月初去了川西的稻城亚丁,体会了金色世界秋日童话,还写了一篇游记<从你的全世界路过-一群程序员的稻城亚丁游记>,也是得到了很多朋友和童鞋的点赞.今年11月初趁着周末的两天时间和朋友去了 ...

  4. 一文教你如何学习TypeScript

    在看这篇文章之前,我是强烈推荐TypeScript 入门教程这本书的.因为这本书它是:从 JavaScript 程序员的角度总结思考,循序渐进的理解TypeScript.文章来源也是该书,但听我一句话 ...

  5. ❤️表弟说看了这本书后,他的TypeScript技术已经登峰造极了!❤️

    在看这篇文章之前,我是强烈推荐TypeScript 入门教程这本书的.因为这本书它是:从 JavaScript 程序员的角度总结思考,循序渐进的理解TypeScript.文章来源也是该书,但听我一句话 ...

  6. JetBrains 开发者调查 - 编程语言趋势

    几个月前在公众号里发布了 StackOverflow 2020 开发者调查结果,其结果对 .NET Core 很友好. 今天我们看看 JetBrains 2017-2020 四年的开发者调查结果统计, ...

  7. .NET和Java之争

    这几天连续有多篇文章诋毁.NET,这类文章我十几年前就看得多了,只不过十几年前是C和C++之争,C++和Java之争.我从来不理这类文章,因为这类口水战并没有什么实际意义. 然而接连收到多位粉丝私聊说 ...

  8. 对小程序框架WePY的精简总结

    大家下午好,萍子最近一直都在写小程序的项目,其中也涉及到了小程序的框架--WePY,讲真的,一开始对这个框架并没有很熟悉,所以也是看了多次它的对应文档,然后特此整理下来,写成博文保存一下,方便日后查看 ...

  9. ApacheCN Asp.NET 译文集 20211126 更新

    ASP.NET Core2 基础知识 零.前言 一.搭建舞台 二.控制器 三.视图 四.模型 五.验证 六.路由 七.RestBuy 八.添加功能.测试和部署 ASP.NET Core3 和 Angu ...

  10. ApacheCN React 译文集 20211118 更新

    React 入门手册 零.前言 一.React 和 UI 设计简介 二.创建组件 三.管理用户交互 React 全栈项目 零.前言 一.使用 MERN 释放 React 应用 二.准备开发环境 三.使 ...

最新文章

  1. poj3468 A Simple Problem with Integers
  2. 记录一次生产环境下的jvm内存泄露问题和分析解决过程!
  3. 高性能WEB开发(6) - web性能测试工具推荐
  4. 超图桌面版区分不同类型数据源的图标
  5. 给计算机系统的资产分配的记号被称为什么,哈工大2015计算机复试试题(25页)-原创力文档...
  6. Java单元测试技巧之PowerMock
  7. 医学图像数据集和处理工具【总结】
  8. pythonsparkfilter_python中的map、filter、reduce函数
  9. 第一期_内存管理单元MMU
  10. Vue 下载本地静态资源
  11. 浏览器对象模型BOM、文档对象模型DOM
  12. ubuntu 12.04下Trackpoint 小红点灵敏度和速度调整
  13. 电脑上面的word文档被删除了怎么办?分享四种亲测恢复方法
  14. Windows安装 choco包管理工具
  15. HDU 6638 Snowy Smile 线段树+最大子段和
  16. 哪款游戏蓝牙耳机好用?好用的游戏蓝牙耳机推荐
  17. Python数据分析辅助审计工作
  18. Kubernetes 1.25 正式发布,多方面重大突破
  19. Linux-Centos7防火墙配置
  20. 串口通信是先发低位再发高位

热门文章

  1. 《linux核心应用命令速查》连载二:lastcomm:显示以前使用过的命令的信息
  2. WEB开发新势力——Openparty
  3. 二十一天学通C++之使用try/catch捕获异常
  4. 《Joel On Software》读后
  5. 【机器学习】xgboost以及lightgbm资料汇总
  6. 树莓派桌面没有时间_树莓派3B/3B+开启手机远程桌面和终端,没有屏幕和电脑的伙伴们有福啦!...
  7. centos安装apache+mysql_CentOS7安装apache+mysql+php环境
  8. 数据结构和算法liuyubobo_C++,java算法与数据结构-某课网价值166元实战教程
  9. python打包后怎么安装_别再问我怎么Python打包成exe了!
  10. python斜率转换为航向0-360_机器学习模型之LinearRegression(Python学习笔记)