我是否滥用useEffect?基于更新查询的数据过滤方案咨询
植物商城过滤功能优化与SEO疑问解答
问题背景
我实现了植物商城的过滤功能:用户点击复选框时过滤商品数据、更新URL查询参数,刷新页面后过滤状态能保留。现有两个核心组件:
ShopPlants:负责展示商品,并基于URL查询参数过滤数据ShopNavPlants:管理过滤状态,同步更新URL查询参数
当前功能正常,但代码显得繁琐,想确认是否存在过度设计或不良实践;另外数据通过getStaticProps从MongoDB预获取,担心前端useEffect过滤会影响SEO效果,恳请给出建议。
现有实现方案
- 基于Next.js的
useRouter监听路由变化 ShopPlants组件以router.isReady和router.query为依赖,路由就绪时执行过滤逻辑ShopNavPlants组件使用两个useEffect:- 结合防抖处理过滤状态变化,同步更新URL查询参数
- 在路由就绪时从URL查询参数恢复过滤状态(解决首次渲染时
router.query为undefined的问题)
现有代码展示
ShopPlants 组件
const ShopPlants = (props) => { // State handles current display of products const [products, setProducts] = useState( sortProducts(props.products, "featured") ); const router = useRouter(); // Handles sorting and updates display const sortHandler = (curSort) => { const sortedProducts = sortProducts(products, curSort); setProducts(sortedProducts); }; // Handles filtering data based on query changes useEffect(() => { // FILTER HERE if (router.isReady) { // checks if query 'features' for filtering exists to filter accordingly if (router.query?.features) { // convert query to an array and return filteredItems const toArray = router.query.features.split("&"); const filteredItems = props.products.filter((product) => { const bool = toArray.map((fitlerName) => product[fitlerName]); return bool.every((item) => item); }); // set current products display to filtered data setProducts(sortProducts(filteredItems, "featured")); return; } // if no filter query is found, return default setProducts(sortProducts(props.products, "featured")); } }, [router.isReady, router.query]); return ( <section className="shop"> <div className="shop__container section-container"> <div className="shop__heading"> <h1 className="heading-secondary">Shop Plants</h1> <ShopSort sortedHandler={sortHandler} /> </div> <div className="shop__main"> <aside className="shop__sidebar"> <ShopNavPlants /> </aside> <div className="shop__products"> {products.map((product) => { return <ProductOverview key={product.id} product={product} />; })} </div> </div> </div> </section> ); };
ShopNavPlants 组件
// TODO: REFACTOR FILTERING, ADD FILTERING HOOK? const filterReducer = (state, action) => { switch (action.type) { case "ADD_FILTER": { const updatedState = [...state]; updatedState.push(action.filter); return updatedState; } case "REMOVE_FILTER": { const updatedState = [...state].filter((name) => { return name !== action.filter; }); return updatedState; } case "ACTIVE_FILTERS": { const updatedState = [...action.filterList]; return updatedState; } default: return state; } }; const ShopNavPlants = () => { const router = useRouter(); const [filterStates, dispatch] = useReducer(filterReducer, []); // Handles filterState change and pushes filters to query useEffect(() => { // prevent error with debounce timer (too many url changes) const debounceTimer = setTimeout(() => { if (!filterStates || filterStates.length === 0) { router.push({}); return; } const filterQuery = filterStates.join("&"); router.query.features = filterQuery; router.push({ query: { ...router.query, }, }); }, 500); return () => clearTimeout(debounceTimer); }, [filterStates]); // Handles any active filters on mount (refresh) useEffect(() => { if (router.isReady) { if (router.query?.features) { dispatch({ type: "ACTIVE_FILTERS", filterList: router.query.features.split("&"), }); } } }, [router.isReady]); const checkboxHandler = (e) => { !e.target.checked ? dispatch({ type: "REMOVE_FILTER", filter: e.target.name }) : dispatch({ type: "ADD_FILTER", filter: e.target.name }); }; return ( <nav> <ul className="shop__sidebar__list-filter"> <li className="shop__sidebar__item"> <input type="checkbox" onChange={checkboxHandler} name="petFriendly" checked={filterStates.includes("petFriendly") ? true : false} /> Pet-Friendly </li> <li className="shop__sidebar__item"> <input type="checkbox" onChange={checkboxHandler} name="lowLight" checked={filterStates.includes("lowLight") ? true : false} /> Low-Light </li> <li className="shop__sidebar__item"> <input type="checkbox" onChange={checkboxHandler} name="lowMaintenance" checked={filterStates.includes("lowMaintenance") ? true : false} /> Low-Maintenance </li> </ul> </nav> ); };
疑问解答与优化建议
一、代码设计优化建议
当前实现功能正常,但可以通过以下方式简化代码、消除潜在问题:
- 提取自定义Hook:把路由监听、过滤状态同步、URL更新这些关联逻辑抽成
useFilter自定义Hook,让两个组件的职责更单一,减少代码冗余。比如Hook可以封装过滤状态管理、URL参数同步、从URL恢复状态的逻辑。 - 简化Reducer逻辑:当前的
filterReducer可以简化,用数组方法精简写法,同时用Set避免重复添加过滤项:const filterReducer = (state, action) => { switch (action.type) { case "ADD_FILTER": return [...new Set([...state, action.filter])]; case "REMOVE_FILTER": return state.filter(name => name !== action.filter); case "ACTIVE_FILTERS": return [...action.filterList]; default: return state; } }; - 优化URL更新逻辑:
- 不要直接修改
router.query对象,创建新的query对象避免副作用 - 清空过滤时移除
features参数,同时使用shallow: true避免页面重载:const filterQuery = filterStates.join("&"); router.push( { query: { ...router.query, features: filterStates.length ? filterQuery : undefined } }, undefined, { shallow: true } // 仅更新路由,不触发页面重新渲染 );
- 不要直接修改
- 避免重复计算:每次过滤都调用
sortProducts,可以将过滤和排序逻辑分离,或者基于原始props数据排序,而非当前展示的products状态,减少不必要的计算。
二、SEO影响确认
当前方案不会影响SEO,原因如下:
- 数据通过
getStaticProps预渲染,页面源码中已经包含全部商品数据,搜索引擎爬虫可以抓取到完整内容 - 前端
useEffect过滤仅在客户端修改DOM展示内容,不会覆盖预渲染的HTML,爬虫看到的是完整的预渲染数据
如果希望过滤后的页面也能被爬虫抓取,可以考虑两种方案:
- 启用Incremental Static Regeneration(ISR):配置
revalidate参数,让Next.js为不同的过滤参数生成静态页面,兼顾静态页面的性能和动态过滤的需求 - 使用
getServerSideProps:在服务端根据URL查询参数过滤数据后再渲染,但这会失去静态页面的性能优势,需要根据业务流量权衡
内容的提问来源于stack exchange,提问作者weezyBee
相关产品推荐
相关产品推荐

