实现一个类型完善的 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;
}
相关推荐
Cacciatore->21 分钟前
Electron 快速上手
javascript·arcgis·electron
vvilkim29 分钟前
Electron 进程间通信(IPC)深度优化指南
前端·javascript·electron
某公司摸鱼前端2 小时前
ES13(ES2022)新特性整理
javascript·ecmascript·es13
ai小鬼头2 小时前
百度秒搭发布:无代码编程如何让普通人轻松打造AI应用?
前端·后端·github
漂流瓶jz2 小时前
清除浮动/避开margin折叠:前端CSS中BFC的特点与限制
前端·css·面试
前端 贾公子2 小时前
在移动端使用 Tailwind CSS (uniapp)
前端·uni-app
散步去海边2 小时前
Cursor 进阶使用教程
前端·ai编程·cursor
清幽竹客2 小时前
vue-30(理解 Nuxt.js 目录结构)
前端·javascript·vue.js
weiweiweb8882 小时前
cesium加载Draco几何压缩数据
前端·javascript·vue.js
幼儿园技术家3 小时前
微信小店与微信小程序简单集成指南
前端