实现一个类型完善的 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;
}
相关推荐
IT_陈寒几秒前
Vite热更新失效?你可能漏了这个配置
前端·人工智能·后端
Revolution6140 分钟前
React 组件重新渲染时,到底重新执行了什么
前端·react.js·面试
用户2181697049302 小时前
Flutter(四)Dart语法 空安全 运算符 流程控制
前端
许彰午2 小时前
政务督办的分合模式:主办协办的并发审批
前端·javascript·政务
用户69371750013842 小时前
AI时代,程序员该往哪走?
前端·后端
ttwuai2 小时前
GoFrame 后台日志清空失败:无 WHERE 删除为什么被拦住
前端·golang
易筋紫容2 小时前
创建型模式:对象的诞生艺术
开发语言·前端·javascript
半句唐诗3 小时前
我是如何通过 Access Token 成功发布第一个 npm 包的
前端·npm·node.js
程序员黑豆3 小时前
鸿蒙应用开发:@Provider 与 @Consumer 跨组件双向同步详解
前端·harmonyos
paopaokaka_luck3 小时前
基于springboot3+vue3的智能文库平台(AI智能搜索、AI智能汇总、实时在线状态展示、多格式文档预览与富文本编辑、Echarts图形化分析)
前端·网络·spring boot·网络协议·echarts