9,862
对于初学TS的同学,最大问题是不擅长处理函数的返回类型,如果一个函数返回的类型是多种或可以为空的,甚至是复合的,或者对原始类型做修改,做组合,做修剪,新手往往需要大量定义相似度高的类型来解决问题。而且单一的类型限定或使用范型,往往容易出现各种类型错误。往往缺少耐心的同学,就会直接用any类型。这样就违背TS使用的初衷了。甚至跟用JS没有什么区别了。搞清楚多种返回类型的技巧和写好工具辅助类型,才是用好TS的关键。
一.使用联合类型和交叉类型
比如我们有这样一个js函数
function fucExp(){
if(...条件){
return {"aa":123}
}else{
return [1,2,3]
这个fucExp函数既可能会返回一个对象,也可能返回一个数组,如果用TS的话,返回类型应该怎么做呢?
这个时候我们应该要用到TS的联合类型了,采用|符号定义 ,我们可以首先定义这个联合类型type,当然也可以直接使用。推荐使用下面这种写法。
type UncertaintyType = Object | number[]
function fucExp(): UncertaintyType {
if (0 < 3) {
return { "aa": 123 }
} else {
return [1, 2, 3]
比如我有这样一个函数,需要合并了两个对象。返回的类型是两个对象的合并类型。我们如何定义他TS的类型呢?
function funcMerge() {
let dog = {
name: "jack",
age: 23,
let behavior = {
bark: () => {
console.log('wang wang')
return Object.assign(dog, behavior)
再次强调,千万不要用any !!!
我们上交叉类型。交叉类型把几个类型的成员合并,使用&符号,可以形成一个拥有这几个类型所有成员的新类型。从字面上理解,可能会误认为是把取出几个类型交叉的(即交集)成员 。(注意:交叉类型是不交集,是合并,这里比较容易错,重点提醒) 正确操作如下
interface Behavior {
bark: Function
interface Dog {
name: string,
age: number
type MergeType = Dog & Behavior
function funcMerge(): MergeType {
let dog: Dog = {
name: "jack",
age: 23,
let behavior: Behavior = {
bark: () => {
console.log('wang wang')
return Object.assign(dog, behavior)
说明:根据你函数实际返回情况&和| 这两种类型都可以混合使用。 如果一个类型里面&和|使用过多,也很恶心,对于更复杂的类型,请看后面的工具类型的详细讲解。
二. never类型
如果我们有一个函数没有任何返回,那怎么定义它的返回类型呢?
你可能马上就会想到JS与此类似的 void 关键字,当一个函数返回空值时,它的返回值为 void 类型,但是,当一个函数永不返回时。在TS中可不能用void。比如我们这个函数只是执行的过程中,发生意外抛出一个错误而已。
TS中never类型会比较优雅的处理这种情况。比如看下面这段代码示例
function neverFunc():never{
throw new Error('Throw my error');
注意:但是除了 never 本身以外,其他任何类型不能赋值给 never
三. 类型捕获
比如有这样一种情况,我们从外部引入的json数据,或从其他第三方lib导入的Object,并没有定义类型,我们需要返回它的类型,应该怎么办呢?
在TS中可以通过 typeof 操作符在类型注解中使用变量。这允许你告诉编译器,一个变量的类型与其他类型相同。代码如下所示
let obj = {
msg:"no"
function unknowReturnFunc() :typeof obj {
obj.msg = "ok"
return obj
不仅能够捕获对象的类型,也可以捕获成员的类型。代码如下所示
let obj = {
msg:"no"
function unknowReturnFunc() :typeof obj.msg {
obj.msg = "ok"
return obj.msg
还有一种情况,我们希望函数返回的类型,限定在指定值的范围,比如我们有animal类型,里面有dog,有cat。 那么返回类型只能是这两种的其中一种,不能是其他的字符串,那么应该怎么做呢?
TS中keyof操作符能让你捕获一个类型的键,这允许你很容易地拥有像字符串枚举,常量这样的类型使用。那么请看下面的代码。
let animal ={
dog:"dog",
cat:"cat"
type AnimalType = keyof typeof animal
function funcKeyOfExp():AnimalType {
let animalObj :AnimalType
animalObj="dog"
animalObj="cat"
return animalObj
从上面例子可以看出,可以很轻易的就实现了一个类似枚举的类型。其实这个就是利用了TS字面类型的特点。具体细节可以去官方手册查看。这里不作详细说明
四. 混合和多重继承
在TS中class不支持多重继承,而且TS中implements只能继承属性,不能继承代码逻辑 。 所以怎么实现呢 。利用函数返回一个扩展构造函数的新类,可以用TS混入的概念模拟多重继承。比如我们有个函数,传入一个类型,需要返回一个新类型,这个新类型是基于老类型的扩展。这是一个mixins操作。
话不多说,看例子:
type Constructor<T = {}> = new (...args: any[]) => T;
function userOne<TBase extends Constructor>(Base: TBase) {
return class extends Base {
myName = "Felix"
function userTwo<TBase extends Constructor>(Base: TBase) {
return class extends Base {
score = 60;
updateScore() {
this.score =100;
class Person {
age =20;
const UserTwo = userTwo(userOne(Person));
const userTwoInstance = new UserTwo()
userTwoInstance.myName
userTwoInstance.updateScore()
userTwoInstance.age
通过上面的例子可以看出,函数利用范型继承,返回一个新类型再配合mixins操作。可以很轻松实现一个多重继承。这个就是TS和混合的概念。
五. 善用几个常用的工具类型
Partial<T>
构造可选的类型
比如我们有一个函数,需要返回一个定义好的类型,但是我们又不希望返回整个类型里面所有字段,只是部分字段,但我们希望不要对已有类型做修改。那应该怎么做呢?这个时候我们就要用到Partial这个工具类型了。
type Partial<T> = {
[K in keyof T]?: T[K]
那我们如何使用这个Partial呢? 看下面例子
interface UserInfo {
id:number,
name:string,
mail:string,
function getUserPartail():Partial<UserInfo>{
const userInfo ={
id:123
return userInfo
Require<T>
构造必填的类型返回
当然存在Partial可选也会存在Require必选。首先我们看一下Require类型的定义
type Require<T> = {
[P in keyof T]-?: T[P];
你会发现,跟上面的Partial类型很相似,在?前面加了一个减号, 这个-?
的作用是在于对原始类型属性里面的可选属性进行剔除,从而变为一个强制传入的一个属性。上面的例子我们使用Require改造一下。
interface UserInfo {
id:number,
name:string,
mail:string,
function getUserPartail():Partial<Require>{
const userInfo ={
id:123,
name:'felix',
mail:'xxx@xx.com'
return userInfo
这个Require的作用,可以在我们calss中提供给外部使用的函数,我们可以限定某些参数的属性必须要有,可以将错误卡在编译这一层,不用走的运行时。减少我们代码实际运行过程中出错的概率。
3.Pick<T, K>
从类型T中挑选部分属性K来构造新的类型
Pick类型是一个很实用的类型。当我们返回的类型的属性某一个字段的是我们不想要的话,我们可以用Pick来剔除这个属性,而不是让它变为可选的,这样可以保证结构的干净和准确性。请看下面例子
interface UserInfo {
id:number,
name:string,
mail:string,
function deleteProperty<T>(obj: T, key:keyof T ): T {
const { [key]: deleted, ...newState } = obj;
return newState as T
function getUserPick(userInfo:Pick<UserInfo, 'id' | 'name'>){
console.log(userInfo)
return userInfo
getUserPick(deleteProperty({
id:123,
name:'felix',
mail:'xxx@xx.com'
},'mail'))
我们在传整个UserInfo类型的时候,是不符合要求的,会提示错误,因为Pick这里只允许传id和name两个字段的属性,所以我们利用deleteProperty这个函数工具辅助删掉mail这个字段。 这样返回一个新的类型,这样就不会报错了。大家可以自己跑一遍代码,会有更清晰的理解。
Record<K, T>
构造一个类型,其属性名为K,属性值为T
type Record<K extends keyof any, T> = {
[P in K]: T;
Exclude<T, U>
从类型T中,剔除所有能赋值给U的属性
type Exclude<T, U> = T extends U ? never : T;
Extract<T, U>
从类型T中提取所有可以赋值给U的类型
type Extract<T, U> = T extends U ? T : never;
Omit<T, K>
从类型T中剔除所有能赋值给K的属性
type Omit<T, K extends keyof T> = Pick<T, Exclude<keyof T, K>>
interface UserInfo {
id:number,
name:string,
mail:string,
type UserOmit = Omit<UserInfo, 'mail'>
function getUserPick():UserOmit{
return {
id:123,
name:"myName"
其实这个Omit跟Pick有点类似,Pick是提取并保留,Omit是提取并剔除。一个相反的概念,但也比较好理解。
ReturnType<T>
由函数类型T的返回值类型构造一个类型
type ReturnType<T extends (...arg: any) => any> = T extends (...arg:any) => infer R ? R : any;
六. 结语
工具类型非常多,而且可以相互组合,比如Omit类型就用到了Pick和Exclude的组合。这里我就不一一介绍了,师傅领进门,修行靠个人。当然如果遇到非常好用的工具类型,我会继续补充。当然GITHUB有很多有用的工具类型库,个人推荐ts-toolbelt 和utility-types 大家可以去了解了解。掌握这些类型技巧,才能写出更Professional的TS代码。