实现一个类型完善的 lodash.get

通过类型计算,为 lodash.get实现完善的出入参类型推断

代码在此,可以体验

前言

众所周知,滥用?.符号会使项目的体积快速增长。点我看看体积是怎么爆炸的

使用 lodash.get工具能安全的取对应对象的值。但是使用_.get无法拥有完整的类型分析,也就是说,咱们写的定义文件如果通过 get 方法取值,则无法享受到 type 类型福利。

能不能有一个既类型完善,又安全的 get方法呢?让咱们来造一个轮子。

效果

解读

  1. 针对传入的对象和 pathChain,能获取到对应的值。
  2. 基于传入的对象,能生成 pathChain,输入正确的 pathChain,能识别出 pathChain 指向对象的类型。
  3. 再也不用写?.来增加项目体积了。

代码实现

我们举例最简单的一种类型,即将对象取值的 pathChain 以.连接,则只需要将传入的 pathChain使用 split 方法拆成数组,并通过循环遍历的方式取值。此处简单实现取值逻辑,着重演示下面类型实现。

类型实现

众所周知,可以用 typescript 的类型来计算,下面来用类型系统根据函数第一个入参 计算出第二个入参 ,和出参的类型

Keyof

Keyof 用于接收对象后,获取对象/数组上面的值。

ts 复制代码
// ts里面 extends 可以作为一个判断节点使用
// 例如 type a = 1 extends number ? true : false
// 此时 a === true
type Keyof<T> = T extends any[]
  ? number
  : T extends Record<any, any>
  ? keyof T
  : never;

Split

Split用于接收访问链路后,分解成数组,并进行后续计算。

ts 复制代码
// infer 用于在递归中获取对应位置的值,例如下方的 infer F用于获取第一个点前面的值
// 如果获取到了,则在第一个问号处可以使用infer 推断出的值,相当于声明了一个变量
// 注意:通过 infer 获取到的值在冒号后面无法正常使用。
type Split<T extends string> = T extends `${infer F}.${infer Rest}`
    // 此处有一个递归
  ? [F, ...Split<Rest>]
  : [T];
  
Split<'1.2.3'> = ['1', '2', '3']

GetChain

GetChain 用于接收第一个参数对象后,生成对象上所有的访问链。

ts 复制代码
 type GetChain<
 // 此处声明 T 是xxx 类型的子类型,K 声明在 T 后面,因此可以使用 T。
 // 注意此处 K 使用了 = 符号,因此是一个默认值。
  T extends Record<any, any> | Array<any> | undefined,
  K extends Keyof<T> = Keyof<Required<T>>
> = T extends undefined
// never 在类型计算中有什么意义待我温习一下补充
  ? never
  : K extends string | number
  // 返回当前找到的 key,并往其value 进行递归
  ? `${K}` | `${K}.${GetChain<Required<T[K]>>}`
  : "";
  {}
  {a?: {b?: number}}
  'a'

PickValue

PickValue 用于传入对象、取值链后,取指定对象的类型。

ts 复制代码
type PickValue<
  O extends Record<any, any>,
  Chain extends string[],
  // Nul用于判断当前元素是否可能为 undefined
  Nul extends boolean = false
  // 注意这里的  Rest extends string[],在 infer 中使用 extends 进线类型判断
  // 需要 ts 4.7 支持
> = Chain extends [infer Head, ...infer Rest extends string[]]
// 类型守卫,ts 有些时候无法正确推断出 Head 的类型
// 可能会有问题这里 Head 的类型为啥不放在 infer  使用 extends 语法进行判断,
// 这里的原因我忘了
  ? Head extends string
  // 还是一个递归
    ? PickValue<
        Required<O>[Head],
        Rest,
        // 将当前元素是否可能为 undefined 进线透传+判断
        // 如果前一个元素可能为 undefined,则当前元素也可能为 undefined
        Nul extends true ? true : undefined extends O[Head] ? true : false
      >
    : O
  : Nul extends true
  ? O | undefined
  : O;
  
  PickVlaue<{a: number}, 'a'> = number

函数声明

函数入参、出参类型

ts 复制代码
// T是第一个入参的类型,P 是第二个入参的类型。
// 如果不在调用函数的时候显式传入类型,ts 会从参数中自动推断。
function _get<T extends any[] | Record<any, any> | null | undefined, P extends GetChain<T>>(
  o: T,
  pathChain: P
): PickValue<T, Split<P>> {
  const path = pathChain.split(".");
  let result = o;
  try {
    for (let i = 0; i < path.length; ++i) {
      result = result[path[i]];
    }
  } catch (e) {}
  return result as any;
}
相关推荐
PHP实战开发录13 分钟前
PHP接口超时后为什么任务还在跑
开发语言·前端·php·开发
今天AI了吗23 分钟前
2026年三大AI桌面智能体横评:Codex vs Hermes vs WorkBuddy
大数据·前端·css·人工智能·架构
kill52237 分钟前
interface vs type:到底该用哪个
前端
敲代码的嘎仔1 小时前
从零实现视频续播 + 学习进度统计:前端心跳、条件更新、GROUP BY 统计全链路拆解
java·前端·数据库·学习·面试·职场和发展·音视频
做萤石二次开发的哈哈1 小时前
海康移动交通四款设备技能接入实战:布控球+取证终端+测速仪+出入口终端,Web/App/小程序移动交通应用快速生成
前端·物联网·萤石开放平台·蓝海aiot一站式工作台·aiot开发·移动交通
HwJack201 小时前
用 HML + CSS + JS 三段式写鸿蒙界面
javascript·css·harmonyos
IT_陈寒1 小时前
我TM竟然被Java的空指针坑了第三次!
前端·人工智能·后端
xcLeigh1 小时前
AI 编程学习路线图:一份覆盖前端、后端、全栈的系统学习计划
前端·人工智能·学习
linux_cfan1 小时前
HTML5 视频交互标注实践:用开源播放器 ZWPlayer 实现热区、测验与分支节点(附接入代码)
前端·javascript·音视频
小羊没烦恼!1 小时前
Office文件的奥秘——.NET平台下不借助Office实现Word、Powerpoint等文件的解析(完)
java·大数据·前端·网络·word·powerpoint·.net