实现一个类型完善的 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;
}
相关推荐
uhakadotcom22 分钟前
Caddy Web服务器初体验:简洁高效的现代选择
前端·面试·github
前端菜鸟来报道24 分钟前
前端react 实现分段进度条
前端·javascript·react.js·进度条
花楸树1 小时前
前端搭建 MCP Client(Web版)+ Server + Agent 实践
前端·人工智能
wuaro1 小时前
RBAC权限控制具体实现
前端·javascript·vue
专业抄代码选手1 小时前
【JS】instanceof 和 typeof 的使用
前端·javascript·面试
用户0079813620971 小时前
6000 字+6 个案例:写给普通人的 MCP 入门指南
前端
用户87612829073741 小时前
前端ai对话框架semi-design-vue
前端·人工智能
干就完了11 小时前
项目中遇到浏览器跨域前端和后端解决方案以及大概过程
前端
我是福福大王1 小时前
前后端SM2加密交互问题解析与解决方案
前端·后端