typescript中的extends和infer使用及详解

 更新时间:2026年04月02日 10:44:02   作者:Young soul2  
文章解释了TypeScript中的约束、逆变、协变、交叉类型、可选链等概念,通过实例展示了infer、extends、as等关键字的使用方法,并列举了加法、减法、乘法、除法、数组长度判断等示例,最后总结了TypeScript的使用经验

extends

extend的意思如果不是用在类的扩展中那么就表示约束在

type Pick<T, K extends keyof T> = {
  [P in K]: T[P];
};

比如下面这个例子:

在Picks中K应该约束在keyof T这个类型中。

infer

infer表示占位符

逆变和协变

  • 协变:子类型赋给父类型
  • 逆变:父类型赋给子类型(设置strictFunctionTypes:true)
  • 双向协变:父类型可以赋给子类型,子类型也可以赋给父类型

{}、Object和object

  • {} 是个空对象,没有索引,如果将某些内容注释为{},则可以是任何一种类型。
  • object 不能接收原始类型,而 {} 和 Object 都可以,这是它们的区别。
  • object 一般会用 Record<string, any> 代替,约束索引类型更加语义化

&交叉类型

合并两个类型

interface IPerson {
  id: string;
  age: number;
}

interface IWorker {
  companyId: string;
}

type IStaff = IPerson & IWorker;

const staff: IStaff = {
  id: 'E1006',
  age: 33,
  companyId: 'EXE'
};

console.dir(staff)

如果是非对象:

type res = 1 & string

此时合并的是never,所以非对象类型合并的必须是同类型

??和?可选链

??表示不为null并且不为undefined

const data = {
    name:1
}
const dong = data.name ?? 'dog';
// 编译后
"use strict";
var _a;
const data = {
    name: 1
};
const dog = (_a = data.name) !== null && _a !== void 0 ? _a : 'dog';

可以看到是表示不为null和undefined才会获取data.name否则是’dog’。

?表示为null或者是undefined和??刚好相反

const data = {
    name:1
}
const dong = data?.name;
// 编译后
"use strict";
const data = {
    name: 1
};
const dong = data === null || data === void 0 ? void 0 : data.name;

可以看到?首先判断是不是null或者undefined,如果是则返回undefined,否则返回data.name。

keyof any

infer和extends

  • infer只能在extends关键字为true的一侧
  • infer x可以理解成一个未知数x,表示待推断的函数参数
type Test<T> = T extends (infer X)[] ? X : never;
// a类型为number | string
let a: Test<number[] | string[]> = '10'

接下来带大家分析一个比较好的例子:

type ParseQueryString<Str extends string>
  = Str extends `${infer Param}&${infer Rest}`
  // Param--a=1  Rest--b=2&c=3 // { a:1 }
  ? MergeParams<ParseParam<Param>, ParseQueryString<Rest>>
  : ParseParam<Str>;

// 将a=1这种格式解析为对象{a:1}
type ParseParam<Param extends string> =
  // a=1  Key--a  Value--1 // { a:1 }
  Param extends `${infer Key}=${infer Value}`
  ? {
    [K in Key]: Value
  } : Record<string, any>;

// {a:1} {b:2,c:3} 用所有的key做对象,如果只是在其中一个对象那么就直接返回,否则合并两个对象的值
type MergeParams<
  OneParam extends Record<string, any>,
  OtherParam extends Record<string, any>
> = {
    // ['a','b','c']
    readonly [Key in keyof OneParam | keyof OtherParam]:
    // 'a' 约束在{a:1}
    Key extends keyof OneParam
    // 'a'是否约束在{b:2,c:3}
    ?
    (Key extends keyof OtherParam
      // 如果'a'同时约束在{a:1}和{b:2,c:3}那么就合并值返回一个列表
      ? MergeValues<OneParam[Key], OtherParam[Key]>
      // 否则返回{a:1}中的1
      : OneParam[Key])
    :
    // 'a'是否约束在{b:2,c:3}中,在就取出值否则不返回
    (Key extends keyof OtherParam
      ? OtherParam[Key]
      : never)
  }


type MergeValues<One, Other> =
  // 两个一样
  One extends Other
  ? One
  // other是个列表
  : Other extends unknown[]
  // 合并列表
  ? [One, ...Other]
  // 直接返回一个列表
  : [One, Other];


function parseQueryString<Str extends string>(queryStr: Str): ParseQueryString<Str> {
  if (!queryStr || !queryStr.length) {
    return {} as any;
  }
  const items = queryStr.split('&');
  const queryObj: any = {};
  items.forEach(item => {
    const [key, value] = item.split('=');
    if (queryObj[key]) {
      if (Array.isArray(queryObj[key])) {
        queryObj[key].push(value);
      } else {
        queryObj[key] = [queryObj[key], value]
      }
    } else {
      queryObj[key] = value;
    }
  });
  return queryObj
}

const res = parseQueryString('a=1&b=2&c=3')
console.log(res);

使用infer实现递归:

type ReverseStr<
  Str extends string,
  Result extends string = ''
> = Str extends `${infer First}${infer Rest}`
  ? ReverseStr<Rest, `${First}${Result}`>
  : Result;

const a = 'hello'
type b = ReverseStr<typeof a>

/*
Str = hello Result = '' First = h Rest = ello
Str = ello Result = 'h' First = e Rest = llo
Str = llo Result = 'eh' First = l Rest = lo
Str = lo Result = 'leh' First = l Rest = o
Str = o Result = 'lleh' First = o Rest = ''
Str = '' Result = 'olleh' First = '' Rest = ''
 */

下面我们来看看综合案例:

加法

type BuildArray<
  Length extends number,
  Ele = unknown,
  Arr extends unknown[] = []
> = Arr['length'] extends Length
  ? Arr
  : BuildArray<Length, Ele, [...Arr, Ele]>;

type Add<Num1 extends number, Num2 extends number> =
  [...BuildArray<Num1>, ...BuildArray<Num2>]['length'];

type addResult = BuildArray<10>

减法

type Subtract<Num1 extends number, Num2 extends number> = 
// 模式匹配占取部分值
    BuildArray<Num1> extends [...arr1: BuildArray<Num2>, ...arr2: infer Rest]
        ? Rest['length']
        : never;
type dResult = Subtract<10,9>

乘法

type Mutiply<
    Num1 extends number,
    Num2 extends number,
    ResultArr extends unknown[] = []
> = Num2 extends 0 ? ResultArr['length']
        : Mutiply<Num1, Subtract<Num2, 1>, [...BuildArray<Num1>, ...ResultArr]>;

type mResult = Mutiply<11,10>

除法

type Divide<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = []
> = Num1 extends 0 ? CountArr['length']
        : Divide<Subtract<Num1, Num2>, Num2, [unknown, ...CountArr]>;

数组长度

type StrLen<
    Str extends string,
    CountArr extends unknown[] = []
> = Str extends `${string}${infer Rest}` 
    ? StrLen<Rest, [...CountArr, unknown]> 
    : CountArr['length']

大于

type GreaterThan<
    Num1 extends number,
    Num2 extends number,
    CountArr extends unknown[] = []
    // 是否相等
> = Num1 extends Num2 
    ? false
    // CountArr长度是否等于Num2
    : CountArr['length'] extends Num2
        ? true
        // CountArr长度是否等于Num1
        : CountArr['length'] extends Num1
            ? false
            // 不断的加1去判断是否和Num1或者Num2相等,如果先和Num2相等,那就说明Num1是大于Num2的
            : GreaterThan<Num1, Num2, [...CountArr, unknown]>;

过滤

type FilterString<T> = {
  [Key in keyof T as T[Key] extends string ? Key: never]: T[Key];
}

as表示重命名,返回 never 代表过滤掉,否则保留。

总结

以上为个人经验,希望能给大家一个参考,也希望大家多多支持脚本之家。

相关文章

  • JS实现的贪吃蛇游戏完整实例

    JS实现的贪吃蛇游戏完整实例

    这篇文章主要介绍了JS实现的贪吃蛇游戏,结合完整实例形式分析了javascript实现贪吃蛇游戏的具体步骤、原理与相关操作技巧,需要的朋友可以参考下
    2019-01-01
  • javascript实现点击图片切换

    javascript实现点击图片切换

    这篇文章主要介绍了javascript实现点击图片切换,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-04-04
  • typescript返回值类型和参数类型的具体使用

    typescript返回值类型和参数类型的具体使用

    本文主要介绍了typescript返回值类型和参数类型的具体使用文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
    2022-06-06
  • JS中的回调函数实例浅析

    JS中的回调函数实例浅析

    这篇文章主要介绍了JS中的回调函数,结合实例形式简单分析了javascript回调函数的感念、功能、使用方法及相关注意事项,需要的朋友可以参考下
    2018-03-03
  • 微信小程序动态显示项目倒计时

    微信小程序动态显示项目倒计时

    这篇文章主要为大家详细介绍了微信小程序动态显示项目倒计时,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2019-06-06
  • tbody元素支持嵌套的注意方法

    tbody元素支持嵌套的注意方法

    tbody元素支持嵌套的注意方法...
    2007-03-03
  • uniapp小程序实战之利用腾讯地图获取定位

    uniapp小程序实战之利用腾讯地图获取定位

    使用uniapp是因为它是一个使用vue.js开发所有前端应用的框架,开发者编写一套代码,可以发布到ios,android,web以及各种小程序,快应用等多个平台,下面这篇文章主要给大家介绍了关于uniapp小程序实战之利用腾讯地图获取定位的相关资料,需要的朋友可以参考下
    2023-02-02
  • javascript encodeURI和encodeURIComponent的比较

    javascript encodeURI和encodeURIComponent的比较

    在进行SaaS前端开发的时候,大家经常会用到两个JavaScriptNative函数:encodeURI 和 encodeURIComponent。这篇文章详细解释这两个函数的用途并比较它们的不同之处
    2010-04-04
  • js中string转int把String类型转化成int类型

    js中string转int把String类型转化成int类型

    今天碰到一个问题,需要把String类型的变量转化成int类型的,js中String转int和Java中不一样,不能直接把Java中的用到js中
    2014-08-08
  • JS中some和every的区别和用法详解

    JS中some和every的区别和用法详解

    every 和 some 都是数组迭代方法,都可以遍历数组,这篇文章主要介绍了JS中some和every的区别和用法,需要的朋友可以参考下
    2023-05-05

最新评论