V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
EasilyJS
V2EX  ›  分享创造

[TS] 分享一个类型支持非常完善的 object path package

  •  
  •   EasilyJS · 2023-05-29 20:54:25 +08:00 · 913 次点击
    这是一个创建于 574 天前的主题,其中的信息可能已经有所发展或是发生改变。

    之前用过涉及到 object path 的一些 package ,但是发现这些 packages 的类型支持不是特别完善,比如 lodash, react-hook-form, formik, mongodb client 等。

    这些 packages ,或多或少存在的几个问题是:

    • 没有类型约束,不能提供代码自动完成;
    • 生成的 path 不是标准的,大部分只支持 'a.b.c' 这种,这种 path 有一些缺点,与其他 package 不统一(例如 yup ),不能区分 object/array ,大部分都是判断如果是数字就当成数组处理,但是实际可能是 number key 的 object ;
    • 不能通过 path 反推出对应的类型。

    因为我在写 react-happy-form 这个开源项目的时候,对 object path 这块需求比较高,所以就自己单独写了 object-standard-path 这个 package:

    • 提供了非常完善的类型支持(包括对一些特殊类型 any, Map 等的处理);
    • 生成的是标准的 path ;
    • 支持使用 path 对类型进行反推。

    使用起来也非常简单,没有任何依赖,1k 不到的包体积,如果有需要的话,大家可以试试 :)

    Repo 地址: https://github.com/react-earth/object-standard-path

    可以的话,可以小点个 star ,每个 star 都是我以后持续更新的动力,感谢!🌟

    import { Path, PathValue, pathGet, pathSet } from 'object-standard-path';
    
    type Test = {
      value: string;
      array: {
        value: string;
      }[];
    };
    
    type TestPath = Path<Test>;
    // result: "value" | "array" | `array[${number}]` | `array[${number}].value`
    
    type TestPathValue = PathValue<Test, 'array[0]'>;
    // result: { value: string }
    
    const object = {
      array: [
        {
          value: 1,
        },
      ],
    };
    
    const result = pathGet(object, 'array[0].value');
    // result: 1
    
    pathSet(object, 'array[0].value', 2);
    // result: { array: [{ value: 2 }] }
    
    10 条回复    2023-05-30 15:43:47 +08:00
    mxT52CRuqR6o5
        1
    mxT52CRuqR6o5  
       2023-05-29 20:59:46 +08:00
    typescript 的泛型实现有没有办法 benchmark ?我现在就有用到类似的东西,感觉性能不是很理想
    EasilyJS
        2
    EasilyJS  
    OP
       2023-05-29 21:05:05 +08:00
    没试过 benchmark ,要找一些有没有相关工具,不过我试了一些比较复杂的数据结构,感觉推断速度还是比较快的
    codehz
        3
    codehz  
       2023-05-29 21:52:27 +08:00
    我觉得对象深度修改还是 lens/optics 的思路好,不依赖 ts 的模版字符串,不需要运行时和编译期 parse 路径两次(然后可能实现不一致)
    用 template literal 的时候,数字要先组合到字符串里,然后再 parse 回来,这在我看来是有点浪费的行为(
    当然用字符串也有字符串的好处,比如说可以整个当作 key 来使用——但 lens 的组合也可以提供这种能力
    ----
    当然最好的方法还是从源头去掉这个需求
    什么情况会用到 path ,如果是为了 form 的递归,那可能有别的思路——比如 hookstate 那样直接在状态管理库上就把递归的问题处理好了,拿到的直接就是一个 State<T, Extensions>对象,T 是最终的类型,不需要考虑原始的容器,对它进行更新即可
    当然我这只是个人的想法,实际应用可能也有不同的问题,hookstate 自己也有很多坑点
    Leviathann
        4
    Leviathann  
       2023-05-29 22:21:56 +08:00
    这个和 ts toolbelt 的 AutoPath / type-fest 的 Get 有什么区别
    EasilyJS
        5
    EasilyJS  
    OP
       2023-05-30 14:38:06 +08:00
    @codehz 目前我 object path 使用最多的场景是 form 表单的构建,针对那些特别复杂的表单需要拆分成若干层级的子组件,每个子组件使用的整个 form 状态的部分 path ,为了保持 form 的健壮性,需要对 path 进行强约束,避免手误导致意料之外的 bug ,如果 form 状态调整了,也可以针对 ts 报错的子组件进行快速调整。
    codehz
        6
    codehz  
       2023-05-30 15:00:05 +08:00   ❤️ 1
    @EasilyJS 看了一眼 happy-form 的使用场景,确实有其合理性,包括其中推导参数类型的内容
    但是如果像我说的用 hookstate 这样的库的话,就可以直接使用原生的.和[]语法来引用子属性,做类似<input {...bind(state.object.array[1].value)} /> 这样的写法,或者也可以直接把提取出来的子 state 传递给自定义组件,就好像它只是普通的 value 一样,hookstate 的 validator 也能在这个提取出来的子 state 上定义,从而从根源上消除了对 object path 的需求
    ----
    此外我担心 object-path 有其局限性,比如关于特殊字符的处理,js object 里可以用任意符号做 key ,当使用了这样的 key 的时候,就很难有一个“正确的”处理方案,当然可以一开始就直接禁止,typescript 里检测到特殊符号就返回 never
    现在的情况是完全没限制,然后 Path<{"a.b": number}>这样的,就会直接出错
    EasilyJS
        7
    EasilyJS  
    OP
       2023-05-30 15:08:25 +08:00
    @codehz 确实目前一些特殊字符不是特别好处理,只能是避免使用,比较好奇如果是 <input {...bind(state.object.array[1].value)} 这种方式,确实能比较好的设置 value ,但是 onChange 回调的时候,如何把值设置回去呢?
    EasilyJS
        8
    EasilyJS  
    OP
       2023-05-30 15:10:34 +08:00
    @Leviathann 我试了下你提到的 type-fest 的 Get ,目前只能通过 path 拿到 value ,并不支持生成 path ,另外没有包含 path get/set 的实现,感觉使用场景比较局限
    codehz
        9
    codehz  
       2023-05-30 15:18:03 +08:00
    @EasilyJS hookstate 可以直接 set(xxx) (当然 get value 也得用.get(),不然拿到的是 State 对象),实际上是用 proxy 实现的
    EasilyJS
        10
    EasilyJS  
    OP
       2023-05-30 15:43:47 +08:00
    @codehz 看了下 hookstate 的示例,用 proxy 确实可以实现,但感觉稍微有点 hack (虽然 Mobx 也是类似原理),而且这样的话不太容易和其他第三方库兼容,比如我需要配合 yup 验证库,去标记错误状态,yup 只会给一个 error 的 path 数组
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5895 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 02:18 · PVG 10:18 · LAX 18:18 · JFK 21:18
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.