Skip to content

前言

手写 Radash:从 0 到 1 构建一个生产级函数式工具库

第一阶段:重新理解工具库的本质

  • 为什么需要手写 radash
    不是为了造轮子,而是为了理解抽象、掌握组合、掌控类型
  • 工具库 ≠ 函数集合,它是“编程原语”的封装
    mapfilterdebounce 都是可复用的计算模式
  • 函数式编程核心:纯函数、不可变、高阶函数、组合
    pipecompose 是函数式流水线的基石
  • TypeScript 优先设计:类型即文档,IDE 零配置智能提示
    泛型、条件类型、映射类型深度应用
  • Tree-shaking 友好:ESM + sideEffects: false,确保只打包用到的函数

第二阶段:深入核心函数实现原理

debouncethrottle

  • 防抖(Debounce):最后一次调用后延迟执行
    实现要点:clearTimeout + setTimeout
  • 节流(Throttle):固定时间间隔内最多执行一次
    实现要点:时间戳比较 or 定时器锁
  • leading vs trailing:为何 Lodash 中二者可选,而某些场景必须互斥?
    用户体验 vs 资源节约的权衡
  • 类型安全:如何推导 debounce(fn, 300) 返回函数的参数与返回类型?
    使用 Parameters<T>ReturnType<T>
  • 实战:为 Vue3 组件的搜索框添加防抖输入

retry

  • 重试机制:为何需要指数退避(Exponential Backoff)?
    避免雪崩效应,给服务恢复时间
  • 随机抖动(Jitter):为何要在退避时间上加随机值?
    防止“重试风暴”,分散请求压力
  • 实现策略:
    • 固定间隔
    • 线性退避
    • 指数退避 + 抖动
  • 错误分类:如何判断哪些错误值得重试?
    网络超时、5xx 错误 vs 4xx 客户端错误
  • 类型安全:如何让 retry(asyncFn) 返回与原函数一致的 Promise 类型?

deepGet / get

  • 安全访问嵌套对象:a.b.c.d 可能因任意层级为 null 而崩溃
    传统写法:a && a.b && a.b.c && a.b.c.d
  • deepGet(obj, 'a.b.c.d')deepGet(obj, ['a', 'b', 'c', 'd'])
    递归遍历路径,任一环节 null/undefined 即返回默认值
  • 性能优化:路径字符串分割 vs 直接数组传入
  • 类型推导:如何让 deepGet<T, K>(obj: T, path: K) 返回 T[K]
    使用 DotPath 工具类型递归解析字符串路径

omit / pick

  • omit(obj, ['a', 'b']):返回移除指定键的新对象
    使用 Object.keys + filter + reduce
  • pick(obj, ['a', 'b']):返回只保留指定键的新对象
  • 不可变性:绝不修改原对象,返回新引用
  • 类型推导:如何让 omit 返回的类型自动排除被删除的键?
    使用 Omit<T, K> + 映射类型

pipe / compose

  • 函数式流水线:pipe(f, g, h)(x) 等价于 h(g(f(x)))
    compose 是反向执行
  • 实现:reduce 累积函数调用
  • 类型推导:如何实现多参数函数的链式传递?
    使用函数重载或泛型数组推导
  • 错误处理:中间函数抛错如何传递?

第三阶段:掌握高级函数与异步处理

asyncPipe / asyncCompose

  • 异步函数流水线:支持 Promise 返回值的 pipe
    使用 async/await + reduce
  • 并行执行:parallel 函数如何实现?
    Promise.all 封装
  • 串行执行:series 函数如何按顺序执行异步任务?
    reduce + async/await
  • 并发控制:concurrency 限制同时执行的任务数
    使用“任务池”模式

sleep

  • await sleep(1000)setTimeout 更优雅
    返回 Promise<void>,可 await
  • **实现:new Promise(resolve => setTimeout(resolve, ms))
  • 用途:模拟延迟、重试间隔、动画节奏

is 工具集

  • 类型判断函数:isString, isObject, isArray, isFunction
    使用 Object.prototype.toString.call()
  • 空值判断:isEmpty, isNil, isUndefined
  • 类型谓词(Type Predicate):isString(x): x is string
    让 TypeScript 编译器在 if 分支中收窄类型

第四阶段:生产级工程化设计

  • 模块化组织:按功能拆分文件(function.ts, object.ts, array.ts
    支持按需导入
  • TypeScript 类型定义:为每个函数编写精确的泛型与返回类型
  • 单元测试:使用 bun testvitest 覆盖边界 case
    debounceleading 触发、retry 的最大重试次数
  • 性能基准测试:hyperfinebenchmark.js 对比 Lodash
  • 文档生成:TypeDoc 自动生成 API 文档
  • 发布流程:npm publish + 语义化版本 + Git Tag

第五阶段:与现代框架深度集成

  • 在 Vue3 中使用 debounce 实现搜索框防抖
    const debouncedSearch = debounce(searchAPI, 300)
  • 在 NestJS 中使用 retry 调用第三方 API
    网络不稳定时自动重试
  • 在 Hono 中使用 pipe 组合中间件逻辑
    函数式风格的请求处理链
  • 在前端构建中实现 Tree-shaking
    Rollup/Vite 如何只打包用到的函数?

第六阶段:未来扩展与深度优化

  • 支持 Deno/Bun:单文件分发,零依赖
  • WebAssembly 优化:将高频函数(如 deepGet)用 Rust 编写
  • 运行时性能监控:为 retry 添加耗时统计
  • 自定义错误类型:RetryError 携带重试次数、原始错误
  • 插件机制:允许扩展新函数