FckSignups 的 useMemo 性能优化实战:如何避免搜索排序中的重复计算
FckSignups 的 useMemo 性能优化实战如何避免搜索排序中的重复计算【免费下载链接】FckSignupsA list of tools that are open-source, in-browser, and require no-signups!项目地址: https://gitcode.com/GitHub_Trending/fc/FckSignupsFckSignups现已更名为 NoSignups是一个聚合了 260 款开源、浏览器内即用、无需注册账号的工具目录核心源码基于 React TypeScript 构建。本文以它的搜索、过滤与排序逻辑为案例讲解如何用useMemo缓存计算结果、避免重复计算是适合前端的 React 性能优化入门教程。为什么选这个项目做 useMemo 案例先看看这个开源工具目录要处理什么 数据源是一份 tools.json包含 260 余条工具记录名称、描述、标签、star 数等 支持按关键词搜索名称、描述、标签多字段匹配 打分️ 支持按分类生产力、设计、开发、隐私等 10 个分类过滤 结果需要按匹配分数 star 数双维度排序 排序后还要拆分成 Featured、Editors Picks、Meets Criteria 三个区块这些计算全都集中在一个自定义 HookuseTools.ts 里。数据量中等、计算链路长、且每次输入都会触发——正是useMemo最典型的使用场景。想本地跑起来练习可以克隆仓库后安装依赖运行git clone https://gitcode.com/GitHub_Trending/fc/FckSignups cd FckSignups npm install npm run dev先看数据流水线4 个 useMemo 各管一段打开 useTools.ts会看到一条清晰的记忆化流水线每个useMemo只负责一步依赖上一步的结果用户输入 │ ▼ ① searchKeywords ── 把 Video Editor 拆成 [video, editor] │ ▼ ② scoredTools ──── 按分类过滤 关键词打分每条工具 0~N 分 │ ▼ ③ filteredTools ── 按分数和 star 数排序 │ ▼ ④ sections ─────── 拆成 featured / editorsPicks / meetsCriteria① 关键词分词最便宜的缓存const searchKeywords useMemo(() tokenize(searchQuery), [searchQuery]);tokenize会把文本转小写、拆成纯字母数字词让video-editor、Video Editor、video_editor都能归一化为[video, editor]见 useTools.ts。分词很便宜但它是后续每一步的输入单独缓存可以让依赖关系更精确。② 过滤 打分整条链路里最重的一步const scoredTools useMemo(() { const keywords searchKeywords; return allTools .filter((tool) activeCategory all || tool.category activeCategory) .map((tool) ({ tool, score: matchScore(tool, keywords) })) .filter(({ score }) keywords.length 0 || score keywords.length); }, [allTools, activeCategory, searchQuery]);这里做了三件事源码注释原话分类过滤过滤掉不属于当前分类的条目关键词打分matchScore会把工具的name description tags拼成一段小写文本统计查询词命中了几个全匹配过滤搜索时只保留全部关键词都命中的工具依赖数组是[allTools, activeCategory, searchQuery]——注意它依赖的是searchQuery字符串而不是searchKeywords数组。因为searchKeywords每次分词都会生成新数组引用若依赖它会导致不必要的重复计算。这是useMemo依赖设计里很实用的一课依赖最原始的原始值比依赖派生值更稳定。③ 排序先复制再排序const filteredTools useMemo( () [...scoredTools] .sort((a, b) b.score - a.score || (b.tool.stars ?? 0) - (a.tool.stars ?? 0)) .map(({ tool }) tool), [scoredTools], );这段代码有两个容易被新手忽略的细节[...scoredTools]展开运算符先拷贝一份再排序。Array.prototype.sort是原地排序直接对缓存数组调用会污染上一步的 memo 结果导致后续依赖它的缓存行为不可预期。记住口诀只读缓存可变就复制。比较函数用||串联两级排序先比匹配分数分数相同时比 star 数。?? 0兜底了 Tool 类型 中stars为可选字段的情况。④ 区块拆分最轻的一步但依赖最稳const sections useMemoToolSections( () sectionize(filteredTools), [filteredTools], );sectionize只做三次filter见 useTools.ts把排序结果按section字段拆成三个数组。它的依赖只有一个filteredTools——只有当上游排序结果真的变化时这里才会重算。useMemo 带来的实际收益在哪里关键在于哪些变化会触发哪些重算。这条链路的依赖关系保证了触发事件① 分词② 过滤打分③ 排序④ 拆块输入一个字符✅✅✅✅切换分类❌✅✅✅无关 state 变化如加载状态❌❌❌❌工具数据初次加载完成❌✅✅✅比如 Tools.tsx 里showMore按钮的展开状态、Tools.tsx 的加载状态这些 UI 层面的 state 变化完全不触碰任何一条搜索计算链路——没有useMemo的话这些重渲染就会白白把 260 条数据的过滤、打分、排序重新跑一遍。另外缓存结果直接驱动了 UI 的渲染优化ToolCard.tsx 中每条卡片都会调用highlightMatches对名称、描述、标签做关键词高亮卡片数量由sections决定。上游数组引用不变下游就能少渲染、少计算。新手常见的 3 个 useMemo 坑结合这份源码列出最容易踩的问题依赖数组写错漏写依赖 → 拿到过期数据stale data多写依赖尤其是不稳定的对象/数组引用→ 缓存形同虚设每次都重算。在缓存结果上做原地修改比如跳过[...scoredTools]这层拷贝直接sort破坏了上游 memo 的可信度。给廉价计算套 useMemouseMemo本身有比较依赖、保存引用的开销。像isSearching searchKeywords.length 0这种一行判断useTools.ts直接计算反而更干净——只缓存贵且可复用的计算。小结可以带走的检查清单用这个开源工具目录的实战代码对照自查✅ 长计算链路拆成多个小 memo而非一个大 memo依赖关系一目了然✅ 每个useMemo的依赖都是稳定的原始值字符串、id、数组引用避免派生数组引用抖动✅ 对缓存数组做任何修改前先复制✅ 缓存结果向下游传递引用让 Tools.tsx 这类渲染组件能白嫖上游的稳定性✅ 加载失败时还有 fallbackData.ts 内嵌数据集兜底——性能优化别以牺牲健壮性为代价想深入研究建议按这个顺序读源码useTools.ts核心链路→ types/index.ts数据结构→ Tools.tsx消费端。数据量不大时useMemo收益有限但它体现的依赖驱动计算思路才是这个项目最值得借鉴的地方。【免费下载链接】FckSignupsA list of tools that are open-source, in-browser, and require no-signups!项目地址: https://gitcode.com/GitHub_Trending/fc/FckSignups创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考