类型体操系列(一)基于参数类型限制函数返回类型

类型体操系列(一)基于参数类型限制函数返回类型

在写react-hook的时候,受到useState的启发,想要写一个根据参数是否存在,来决定返回的类型。useState的行为是这样子的

import { useState } from 'react';

const Component = () => {
    // state 类型是 number | null
    const [state, setState] = useState<number>();
    // state 类型是 number
    const [state, setState] = useState<number>(1);
}

而我的hook函数想要的是能根据参数的类型来推导出函数的返回类型,如下

interface Dog {
  age: number;
}

interface Cat {
  weight: number;
}

export const useMyHook = (id?: string) => {
  return typeof id === 'string' ? ({ age: 1 } as Dog) : ({ weight: 10 } as Cat);
};

// Dog | Cat
useMyHook();
// Dog | Cat
useMyHook('asd');
Playground

这种写法无法根据参数的类型来 narrow 函数的返回类型。这时候我首先想到的方法是利用 Conditional Type 来做 narrowing

进一步 Conditional Type

interface Dog {
  age: number;
}

interface Cat {
  weight: number;
}
// 此处提示错误
export const useMyHook = <T extends string>(
  id?: T
): T extends string ? Dog : Cat => {
  return typeof id === 'string' ? { age: 1 } : { weight: 10 };
};

// Dog | Cat
useMyHook();
// Dog | Cat
useMyHook('asd');
Playground

这里是个错误小插曲,要使用conditional type 需要先声明再实现。

再进一步:函数重载

这里就需要利用到函数重载中的的声明和实现的概念,修改如下

interface Dog {
  age: number;
}

interface Cat {
  weight: number;
}

// 由于箭头函数没法重载,这里就改成使用`function`来写
export function useMyHook<T extends string>(
  id?: T
): T extends string ? Dog : Cat;

export function useMyHook<T extends string>(id?: T): Dog | Cat {
  return typeof id === 'string' ? { age: 1 } : { weight: 10 };
}

// test1 : Dog
useMyHook('asd');
// test2 : Dog
useMyHook();
Playground

但还是不行,因为asd 是可选的,而后面跟的T无法被推导为undefined,若将声明处的问号去掉,则使用的时候,一定需要一个参数,这就违背了我们利用可选参数的初衷了。

解决方法还是有的,我们可以直接再实现中,显式的声明参数的类型,同时去掉模板,此时TypeScript就会将实现中的参数类型作为声明中的模板参数,此时就能够触发conditional type

interface Dog {
  age: number;
}

interface Cat {
  weight: number;
}

// 这里直接写成 T 它由函数的实现来约束
export function useMyHook<T>(id?: T): T extends string ? Dog : Cat;

export function useMyHook(id: string | void): Dog | Cat {
  return typeof id === 'string' ? { age: 1 } : { weight: 10 };
}

// Dog
useMyHook('asd');
// Cat
useMyHook();
Playground

这里利用到void这个关键词,也就是在调用useMyHook时,可以忽略参数,同时不用在函数参数的后面加问号。

最后

通过conditional type以及泛型函数和类型推导,就能实现不同的参数类型有不同的函数返回类型。这能够减少在开发时,返回值判空的代码,同时可以缩小整个返回对象的大小,进一步体验到类型系统带来的IDE提示的便利。

展示评论