前言
手写 Radash:从 0 到 1 构建一个生产级函数式工具库
第一阶段:重新理解工具库的本质
- 为什么需要手写
radash?
不是为了造轮子,而是为了理解抽象、掌握组合、掌控类型 - 工具库 ≠ 函数集合,它是“编程原语”的封装
map、filter、debounce都是可复用的计算模式 - 函数式编程核心:纯函数、不可变、高阶函数、组合
pipe、compose是函数式流水线的基石 - TypeScript 优先设计:类型即文档,IDE 零配置智能提示
泛型、条件类型、映射类型深度应用 - Tree-shaking 友好:ESM + sideEffects: false,确保只打包用到的函数
第二阶段:深入核心函数实现原理
debounce 与 throttle
- 防抖(Debounce):最后一次调用后延迟执行
实现要点:clearTimeout+setTimeout - 节流(Throttle):固定时间间隔内最多执行一次
实现要点:时间戳比较 or 定时器锁 leadingvstrailing:为何 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+reducepick(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 test或vitest覆盖边界 case
如debounce的leading触发、retry的最大重试次数 - 性能基准测试:
hyperfine或benchmark.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携带重试次数、原始错误 - 插件机制:允许扩展新函数