React Hook多过滤器优化:函数链式调用解耦实现方案
你的搜索栏功能已经跑通了,但确实存在函数耦合度高、单个函数要处理多种分支场景的问题——比如callFilterByType里直接嵌套调用callFilterByYear,两个函数互相依赖,而且useEffect里的逻辑把搜索、类型过滤、年份过滤揉在了一起,后续维护起来会越来越麻烦。
链式调用刚好能完美解决这个问题!它的核心思路是:把每个独立的过滤逻辑拆成纯函数,然后让这些函数依次执行,前一个函数的输出直接作为后一个函数的输入,就像流水线一样,每个环节只做一件事,逻辑清晰还完全解耦。
第一步:拆分独立的过滤纯函数
先把搜索、类型过滤、年份过滤拆成三个独立的纯函数,每个函数只接收「当前数据数组」和「过滤条件」,返回过滤后的新数组,完全不依赖外部状态:
// 按标题搜索过滤 const filterBySearch = (animes, searchValue) => { if (!searchValue) return animes; const regex = new RegExp(searchValue, "gi"); return animes.filter(anime => regex.test(anime.title)); }; // 按类型过滤 const filterByType = (animes, type) => { if (!type) return animes; return animes.filter(anime => anime.type === type); }; // 按年份过滤 const filterByYear = (animes, year) => { if (!year) return animes; const regex = new RegExp(year, "gi"); return animes.filter(anime => regex.test(anime.start_date)); };
这些都是纯函数:没有副作用,输入确定输出就确定,不仅好理解,还非常容易单独测试和复用。
第二步:用「链式流水线」组合过滤逻辑
现在我们可以把这些函数串联起来,用数组的reduce方法实现链式处理——reduce会依次执行每个过滤函数,把前一次的结果传给下一个函数,最终得到最终的过滤结果:
把原来复杂的useEffect逻辑替换成简洁的链式处理:
useEffect(() => { // 定义过滤流水线:按顺序执行搜索、类型、年份过滤 const filteredAnimes = [ (currentRes) => filterBySearch(currentRes, searchValue), (currentRes) => filterByType(currentRes, filterByType), (currentRes) => filterByYear(currentRes, filterByYear) ].reduce((prevResult, filterFn) => filterFn(prevResult), animes); setSortedAnimes(filteredAnimes); }, [searchValue, animes, filterByType, filterByYear]);
这里的逻辑很清晰:
- 把三个过滤函数包装成接收当前结果的函数,放到一个数组里
- 用
reduce从初始的animes数组开始,依次执行每个过滤函数 - 最后把最终的过滤结果赋值给
sortedAnimes
如果你觉得这种方式不够直观,也可以用原生数组方法的链式写法(本质和上面完全一致):
useEffect(() => { const filteredAnimes = filterBySearch(animes, searchValue) .filter(anime => filterByType ? anime.type === filterByType : true) .filter(anime => filterByYear ? new RegExp(filterByYear, "gi").test(anime.start_date) : true); setSortedAnimes(filteredAnimes); }, [searchValue, animes, filterByType, filterByYear]);
不过第一种用reduce的方式更灵活——以后要加新的过滤条件(比如按评分过滤),只需要新增一个纯函数,加到reduce的数组里就行,完全不用修改其他逻辑。
第三步:完整修改后的App组件
把上面的逻辑整合到你的App组件里,最终代码如下:
import React, { useState, useEffect } from "react"; import Search from "./Search"; import Anime from "./Anime"; import "./App.css"; const KIJAN_API_URL = "https://api.jikan.moe/v3/top/anime/1/upcoming"; // 独立的过滤纯函数 const filterBySearch = (animes, searchValue) => { if (!searchValue) return animes; const regex = new RegExp(searchValue, "gi"); return animes.filter(anime => regex.test(anime.title)); }; const filterByType = (animes, type) => { if (!type) return animes; return animes.filter(anime => anime.type === type); }; const filterByYear = (animes, year) => { if (!year) return animes; const regex = new RegExp(year, "gi"); return animes.filter(anime => regex.test(anime.start_date)); }; const App = () => { const [animes, setAnimes] = useState([]); const [sortedAnimes, setSortedAnimes] = useState([]); const [searchValue, setSearchValue] = useState(""); const [filterByType, setFilterByType] = useState(""); const [filterByYear, setFilterByYear] = useState(""); useEffect(() => { fetch(KIJAN_API_URL) .then(response => { if (response.ok) { return response.json(); } else { throw new Error("Something went wrong"); } }) .then(jsonResponse => { setAnimes(jsonResponse.top); }) .catch(error => { console.log(error); }); }, []); useEffect(() => { // 链式过滤流水线 const filteredAnimes = [ (res) => filterBySearch(res, searchValue), (res) => filterByType(res, filterByType), (res) => filterByYear(res, filterByYear) ].reduce((current, fn) => fn(current), animes); setSortedAnimes(filteredAnimes); }, [searchValue, animes, filterByType, filterByYear]); return ( <div className="App"> <Search searchValue={searchValue} setSearchValue={setSearchValue} filterByType={filterByType} setFilterByType={setFilterByType} filterByYear={filterByYear} setFilterByYear={setFilterByYear} /> {sortedAnimes.length > 0 ? ( sortedAnimes.map((anime, index) => { return <Anime key={index} anime={anime} />; }) ) : ( <span>Aucune correspondance</span> )} </div> ); }; export default App;
为什么这样更好?
- 职责单一:每个过滤函数只做一件事,比如
filterBySearch只处理搜索,不用关心类型和年份过滤 - 解耦:函数之间不再互相依赖,修改其中一个过滤逻辑不会影响其他函数
- 易维护:以后要加新的过滤条件,只需要新增一个纯函数,加到
reduce的数组里就行 - 易测试:每个纯函数都可以单独测试,比如给
filterBySearch传不同的数组和搜索词,验证输出是否正确
简单理解链式调用
链式调用的核心就是让函数执行后返回一个可以继续调用方法的对象/值,比如我们常用的数组方法:
// 这就是链式调用:map返回新数组,然后可以继续调用filter,filter返回新数组再调用sort const result = [1,2,3,4].map(num => num*2).filter(num => num>3).sort((a,b) => b-a);
我们这里的过滤逻辑,就是把每个过滤步骤做成类似数组方法的纯函数,让它们依次处理数据,实现链式的流水线处理~
内容的提问来源于stack exchange,提问作者ArBabacar_

