Ts学习分享

函数重载

使用场景

当我们在调用函数需要根据不同的传参、和参数类型的不同返回不同的结果时,使用函数重载可以给出更友好的提示

function print(text: string, length: number): number;
function print(text: string): string;
function print(text: string, length: number){
    if(typeof text === 'string' && length){
        return length
    }
    return text
}

print('111', 111)
print('222')

这里假设我们有这么一个需求:

有一个包含userInfoloadingState

interface IState {
  loading: boolean;
  userInfo: {
    id: string;
    name: string;
  }
}

const defaultState: IState = {
  loading: false,
  userInfo: {
    id: '1',
    name: 'huolihua'
  }
}

还有一个useAppState用来查询该State,查询有两种方式:

const state = useAppState()  // defaultState
const userInfo = useAppState(state => state.userInfo)  // userInfo

在实现useAppState时如何使用Ts给出比较友好的提示呢?像下图这样:

image
image

实现:

function useAppState<T>(selector: (state: IState) => T): T;
function useAppState(): IState;
function useAppState<T>(selector?: (state: IState) => T) {
  if (!selector) {
    return defaultState
  }
  return selector(defaultState)
}

const state = useAppState()  // defaultState
const userInfo = useAppState(state => state.userInfo)  // userInfo

可辨识联合

先看下面这块代码

interface ICat {
  color: string;
  move: string;
}

interface IDog {
  eat: string;
  cry: string;
}

function print(animal: ICat | IDog) {
  console.log(animal.color)    //报错
}

当 TypeScript 不确定一个联合类型的变量到底是哪个类型的时候,我们只能访问此联合类型的所有类型里共有的属性或方法,我们可以通过

为联合类型增加一个可辨识的特征type如下:

interface ICat {
  color: string;
  move: string;
  type: 'cat'
}

interface IDog {
  eat: string;
  cry: string;
  type: 'dog'
}

这个时候我们增加了一个type来区分两个类型,通过type我们可以做以下判断来访问正确的属性;而且这个时候ts会自动为我们推导出正确的类型。

function print(animal: ICat | IDog) {
  if(animal.type === 'cat'){
    console.log(animal.color)
  }
}

<img src="https://huolihua-1256524053.cos.ap-nanjing.myqcloud.com/image-20220308155731964.png" alt="image-20220308155731964" style="zoom:67%;" />

可辨识联合给我的感觉也就是一种类型保护机制。

Ts类型保护的使用

我所知道的类型保护有以下几种

  • 使用is
  • 使用in,判断一个属性是不是属于某一个对象
  • 使用typeof
  • 使用instanceof,判断一个实例是不是属于某个类

先定义两种类型

interface ITeacher {
  subject: string;
}

interface IStudent {
  name: string;
  age: string;
}

使用is:

function isTeacherWithIs(arg: ITeacher | IStudent): arg is ITeacher {
  return (arg as ITeacher).subject !== undefined
}

function print(arg: ITeacher | IStudent) {
  if (isTeacherWithIs(arg)) {
    console.log(arg.subject)
  }
}

使用in:

function isTeacherWithIn(arg: ITeacher | IStudent){
  if('name' in arg){
    // arg 是 IStudent
    console.log(arg.age)
  }
}

使用typeof:

function padLeft(value: string, padding: string | number) {
    if (typeof padding === "number") {
        return Array(padding + 1).join(" ") + value;
    }
    if (typeof padding === "string") {
        return padding + value;
    }
    throw new Error(`Expected string or number, got '${padding}'.`);
}

这些 typeof类型保护只有两种形式能被识别: typeof v === "typename"typeof v !== "typename""typename"必须是 "number""string""boolean""symbol"。 但是TypeScript并不会阻止你与其它字符串比较,语言不会把那些表达式识别为类型保护。

使用instanceof,判断一个实例是不是属于某个类

interface Padder {
    getPaddingString(): string
}

class SpaceRepeatingPadder implements Padder {
    constructor(private numSpaces: number) { }
    getPaddingString() {
        return Array(this.numSpaces + 1).join(" ");
    }
}

class StringPadder implements Padder {
    constructor(private value: string) { }
    getPaddingString() {
        return this.value;
    }
}

function getRandomPadder() {
    return Math.random() < 0.5 ?
        new SpaceRepeatingPadder(4) :
        new StringPadder("  ");
}

// 类型为SpaceRepeatingPadder | StringPadder
let padder: Padder = getRandomPadder();

if (padder instanceof SpaceRepeatingPadder) {
    padder; // 类型细化为'SpaceRepeatingPadder'
}
if (padder instanceof StringPadder) {
    padder; // 类型细化为'StringPadder'
}

instanceof的右侧要求是一个构造函数,TypeScript将细化为:

  1. 此构造函数的 prototype属性的类型,如果它的类型不为 any的话
  2. 构造签名所返回的类型的联合

以此顺序。

关于类型约束extends的使用

extends约束可以做为条件判断来使用像这样:

type Fly<T> = T extends 'Bird' ? 'Yes' : 'No'

type isFlyWithBird = Fly<'Bird'>  // Yes
type isFlyWithPig = Fly<'Pig'>    // No

常见的使用场景比如网络请求中判断是不是分页?分页的话返回分页的数据结构,否则返回默认的结构

interface PageResponse<T> {
  pageSize: number;
  pageNumber: number;
  pageTotal: number;
  content: T[]
}
// T:自定义类型,P:是否分页
type ResponseData<T, P> = P extends true ? PageResponse<T> : T

我们模仿一个get请求:

function get<T, P = false>(target: ResponseData<T, P>): ResponseData<T, P> {
  return target
}

调用一下试试

interface UserInfo {
  name: string;
  age: number;
}

const res = get<UserInfo, false>({ name: '会飞的Pig', age: 20 })  // res类型为UserInfo
const res2 = get<UserInfo, true>({
  pageNumber: 0,
  pageSize: 10,
  pageTotal: 10,
  content: [{ name: '会飞的Pig', age: 20 }]
})  // res2类型为PageResponse<UserInfo>

这个时候从请求结果中取值时Ts能帮我们推导出正确的类型:

image

类型递归

interface IPeople {
  name: string;
  age: number;
  children: {
    gender: string;
    nation: string;
    address: {
      city: string;
    }
  }
}

思考一个问题:如何将IPeople的属性提取出来作为类型约束呢? 像这种name | age | gender | nation

先看答案:

type Keys<T> =
  T extends object ? { [K in keyof T]-?: K | Keys<T[K]> } [keyof T] : never;

// "name" | "age" | "children" | "gender" | "nation" | "address" | "city"
type NewType = Keys<IPeople>
  1. 先判断传进来的IPeople是不是对象,如果不是对象的话直接返回never; 关于neverTs官网是这样描述的 :“never类型是那些总是会抛出异常或根本就不会有返回值的函数表达式或箭头函数表达式的返回值类型”

  2. {[K in keyof T]-?: K | Keys<T[K]>} 返回一个新的对象如下图:

    <img src="https://huolihua-1256524053.cos.ap-nanjing.myqcloud.com/image-20220309142510592.png" alt="image-20220309142510592" style="zoom:67%;" />

  3. 拿到这个对象只要取出对象的键值就是我们想要的结果了,在JS中取对象的键值可以使用Object[key]在Ts中也是可以的,所以这里使用[keyof T]来取新对象的键值。

keyof的作用就是返回一个对象类型的作为联合类型

interface Obj{
  name: string;
  age: number
}

type D = keyof Obj    // 'name' | 'age'

type E = Obj['name' | 'age']  // string | number

in用于取联合类型的值,主要用于数组和对象的构造

type name = 'firstName' | 'lastName';
type TName = {
  [key in name]: string;
};    // {firstName: string; lastName: string}

-?的作用是将映射类型的属性变为必选。Required实现原理就是用它:

/**
 * Make all properties in T required
 */
type Required<T> = {
    [P in keyof T]-?: T[P];
};

常见的几种高级类型

Partial: 使T类型的所有属性变为可选。

type Partial<T> = {
    [P in keyof T]?: T[P];
};

主要还是使用keyof拿到T类型的然后使用in构造一个新的对象

Readonly: 使T类型的所有属性变为只读。

type Readonly<T> = {
    readonly [P in keyof T]: T[P];
};

ReturnType: 获取函数类型的返回类型

/**
 * Obtain the return type of a function type
 */
type ReturnType<T extends (...args: any) => any> = T extends (...args: any) => infer R ? R : any;

关于infer: 表示在 extends 条件语句中待推断的类型变量,但是只能在为true的分支中使用;

Parameters: 获取函数的参数类型

/**
 * Obtain the parameters of a function type in a tuple
 */
type Parameters<T extends (...args: any) => any> = T extends (...args: infer P) => any ? P : never;

Exclude:从T中排除那些可赋给U的类型

/**
 * Exclude from T those types that are assignable to U
 */
type Exclude<T, U> = T extends U ? never : T;

Extract: 从T中提取那些可以赋值给U的类型

/**
 * Extract from T those types that are assignable to U
 */
type Extract<T, U> = T extends U ? T : never;

Omit: 从对象T中去除满足U类型的属性

/**
 * Construct a type with the properties of T except for those in type K.
 */
type Omit<T, K extends keyof any> = Pick<T, Exclude<keyof T, K>>;

/**
 * From T, pick a set of properties whose keys are in the union K
 */
type Pick<T, K extends keyof T> = {
    [P in K]: T[P];
};

Record: 约束一个对象的key类型和value类型

/**
 * Construct a type with a set of properties K of type T
 */
type Record<K extends keyof any, T> = {
    [P in K]: T;
};
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,294评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,493评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,790评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,595评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,718评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,906评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,053评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,797评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,250评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,570评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,711评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,388评论 4 332
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,018评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,796评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,023评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,461评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,595评论 2 350

推荐阅读更多精彩内容